Maison  >  Article  >  développement back-end  >  10 problèmes de sécurité courants en PHP (explication avec exemples)

10 problèmes de sécurité courants en PHP (explication avec exemples)

Guanhui
Guanhuiavant
2020-06-06 09:08:482557parcourir

10 problèmes de sécurité courants en PHP (explication avec exemples)

Par rapport à d'autres langages, PHP présente de plus grands avantages dans la création de sites Web. Même un novice peut facilement créer un site Web. Mais cet avantage peut aussi facilement entraîner des effets négatifs, car de nombreux tutoriels PHP n'impliquent aucune connaissance en matière de sécurité.

Cet article est divisé en plusieurs parties, chacune couvrant différentes menaces de sécurité et contre-mesures. Cependant, cela ne signifie pas qu’après avoir effectué ces points, vous éviterez définitivement tout problème avec votre site Web. Si vous souhaitez améliorer la sécurité de votre site Web, vous devez continuer à étudier comment améliorer la sécurité de votre site Web en lisant des livres ou des articles

À des fins de démonstration, le code peut ne pas être parfait. Dans le processus de développement quotidien, une grande partie du code est incluse dans des frameworks et diverses bibliothèques. En tant que développeur backend, vous devez non seulement maîtriser les bases de CURD, mais également savoir comment protéger vos données.

1. Injection SQL

Je parie un paquet de lanières épicées que vous verrez ça. L'injection SQL est l'une des plus grandes menaces pour votre site Web. Si votre base de données est attaquée par l'injection SQL de quelqu'un d'autre, quelqu'un d'autre peut vider votre base de données et avoir des conséquences plus graves.

Pour obtenir des données dynamiques de la base de données, le site Web doit exécuter des instructions SQL, par exemple :

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

L'attaquant contrôle les requêtes envoyées via GET et POST (ou d'autres requêtes telles que UA) . Généralement, l'instruction SQL que vous souhaitez interroger pour un utilisateur avec le nom d'utilisateur « peter » est la suivante :

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

Cependant, l'attaquant envoie un paramètre de nom d'utilisateur spécifique, par exemple : 'OR '1'='1

Cela fera en sorte que l'instruction SQL devienne comme ceci :

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

De cette façon, il pourra exporter les données de l'intégralité de votre table utilisateur sans nécessiter de mot de passe.

Alors, comment éviter que de tels accidents ne se produisent ? Il existe deux solutions principales. Échapper aux données saisies par l'utilisateur ou utiliser des instructions encapsulées. La méthode d'échappement consiste à encapsuler une fonction pour filtrer les données soumises par l'utilisateur et supprimer les balises nuisibles. Cependant, je ne recommande pas cette méthode car il est facile d’oublier de la faire partout.

Ensuite, je vais vous présenter comment utiliser PDO pour exécuter des instructions encapsulées (la même chose est vraie pour 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();

Chaque partie des données dynamiques est préfixée par :. Ensuite, transmettez tous les arguments à la fonction d'exécution sous forme de tableau et il semblera que PDO a échappé aux mauvaises données pour vous.

Presque tous les pilotes de bases de données prennent en charge les instructions encapsulées, il n'y a aucune raison de ne pas les utiliser ! Prenez l’habitude de les utiliser et vous ne les oublierez pas plus tard.

2. XSS

XSS est également appelé CSS (Cross Site Script), une attaque de script intersite. Il s'agit d'un attaquant malveillant qui insère du code HTML malveillant dans une page Web. Lorsqu'un utilisateur parcourt la page, le code HTML intégré dans le Web sera exécuté, atteignant ainsi l'objectif particulier d'attaquer l'utilisateur de manière malveillante.

Ce qui suit prend comme exemple une page de recherche :

<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>

Parce que nous imprimons le contenu de l'utilisateur directement sans aucun filtrage, les utilisateurs illégaux peuvent fusionner des URL :

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

PHP The Le contenu rendu est le suivant. Vous pouvez voir que le code Javascript sera exécuté directement :

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

Q : Quel est le problème avec l'exécution du code JS ?

Javascript peut :

voler les cookies dans le navigateur de votre utilisateur ;

obtenir le compte de connexion et le mot de passe de votre site grâce à la fonction de mémorisation du mot de passe du navigateur ;

Voler informations confidentielles des utilisateurs ;

Tout ce que vos utilisateurs peuvent faire sur le site peut être fait avec les autorisations d'exécution des autorisations JS, ce qui signifie que l'utilisateur A peut usurper l'identité de n'importe qui

intégrer du code malveillant dans votre. page Web ;

...

Q : Comment éviter ce problème ?

La bonne nouvelle est que les navigateurs plus avancés disposent désormais de fonctionnalités de base de prévention XSS, mais ne vous y fiez pas.

L'approche correcte consiste à ne faire absolument confiance à aucune entrée de l'utilisateur et à filtrer tous les caractères spéciaux de l'entrée. Cela éliminera la plupart des attaques XSS :

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

Ou vous pouvez utiliser le moteur de modèles Twig. Les moteurs de modèles généraux ajouteront la prévention des entités html à la sortie par défaut.

Si vous conservez le contenu saisi par l'utilisateur, faites particulièrement attention lors de sa sortie. Dans l'exemple suivant, nous permettons aux utilisateurs de remplir leurs propres liens de blog :

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

Le code ci-dessus peut être le. d'abord, il n'y a aucun problème, mais supposons que l'utilisateur remplisse le contenu suivant :

#" onclick="alert(1)

sera rendu comme :


  Visit Users homepage

Ne faites jamais confiance aux données saisies par l'utilisateur, ou, supposez toujours que le contenu de l'utilisateur est offensant, que son attitude est correcte et que chaque entrée et sortie de l'utilisateur est soigneusement traitée.

Lors de la configuration des cookies, si la lecture JS n'est pas requise, veuillez la définir sur "HTTP UNIQUEMENT". Ce paramètre empêche JavaScript de lire les cookies du côté PHP.

3. Exécutez quelques opérations.

虽然此处展示的例子是 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. 登录限制

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

一些其他补充

  • Ne faites pas confiance à l'ID d'objet qui vous a été transmis par l'utilisateur, vérifiez toujours l'accès de l'utilisateur à l'objet demandé

  • Le serveur est toujours à jour date avec les bibliothèques utilisées

  • Abonnez-vous aux blogs liés à la sécurité pour en savoir plus sur les dernières solutions

  • Ne jamais enregistrer les mots de passe des utilisateurs dans les journaux

  • Ne stockez pas l'intégralité de la base de code dans le répertoire racine WEB

  • Ne créez jamais de référentiel Git dans le répertoire racine WEB sauf si vous souhaitez divulguer le base de code entière

  • Toujours supposer que la saisie de l'utilisateur n'est pas sécurisée

  • Configurer le système pour interdire l'affichage IP de comportements suspects, tels que : analyse aléatoire des URL par des outils et des robots d'exploration

  • Ne faites pas trop confiance à la sécurité du code tiers

  • N'utilisez pas Composer pour obtenir du code directement depuis Github

  • Si vous ne souhaitez pas que votre site soit iframe multi-domaines par des tiers, veuillez définir l'en-tête anti-iframe

  • Il n'est pas prudent d'être ambigu

  • Si vous manquez d'opérateurs ou de co-développeurs d'expérience pratique, assurez-vous de vérifier le code aussi souvent que possible

  • Lorsque vous ne comprenez pas comment une fonctionnalité de sécurité est censée fonctionner, ou pourquoi elle est installée, demandez à quelqu'un qui sait, Ne l'ignorez pas

  • N'écrivez jamais le vôtre le cryptage, c'est probablement une mauvaise approche

  • Si vous n'avez pas assez d'entropie, amorcez-la correctement Vos nombres pseudo-aléatoires sont générés et supprimés

  • Si Internet n'est pas sécurisé et qu'il existe une possibilité de vol d'informations, préparez-vous à ce scénario et mettez en place un plan de réponse aux incidents

  • Désactivez l'affichage de la liste du répertoire racine WEB, de nombreuses configurations de serveur WEB répertorieront le contenu du répertoire par défaut, ce qui peut entraîner des fuites de données

  • La vérification du client n'est pas suffisante, il faut tout vérifier à nouveau en PHP

Conseils

Je ne suis pas un expert en sécurité, j'ai bien peur de ne pas pouvoir tout faire en détail. Bien que l'écriture de logiciels sécurisés puisse être un processus pénible, il est possible d'écrire des applications raisonnablement sécurisées en suivant quelques règles de base. En fait, de nombreux frameworks nous ont aidé à faire beaucoup de travail à cet égard.

Les problèmes de sécurité ne ressemblent pas à des erreurs grammaticales qui peuvent être suivies pendant la phase de développement avant que le problème ne survienne. Par conséquent, lorsque vous écrivez du code, vous devez toujours veiller à éviter les risques de sécurité. Si vous êtes obligé d’ignorer temporairement certaines mesures de sécurité en raison de la pression exercée par les besoins de votre entreprise, je pense que vous devez informer tout le monde à l’avance des risques potentiels liés à cette décision.

Si vous avez bénéficié de cet article, partagez-le avec vos amis et laissez-nous construire ensemble un site Web sécurisé.

Tutoriel recommandé : "Tutoriel PHP"

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer