Maison >développement back-end >Problème PHP >Que devez-vous prendre en compte lors du développement de la sécurité PHP ?
Que devez-vous prendre en compte lors du développement de la sécurité PHP ?
1, Saisir la structure de l'ensemble du site pour éviter les fuites de répertoires sensibles du site
Quand j'ai commencé à écrire du code, j'étais comme beaucoup d'anciens codes sources, placez index.php, register.php, login.php dans le répertoire racine Lorsque l'utilisateur clique sur la page d'inscription, il passera à http://localhost/register.php
. Il n'y a pas beaucoup de réflexion structurelle. Avec une structure de code comme celle-ci, le plus gros problème n'est pas la sécurité, mais l'expansion et la transplantation du code.
Dans le processus d'écriture du code, nous devons souvent modifier le code à ce stade, si le code n'a pas de point d'entrée unifié, nous devrons peut-être changer de nombreux endroits. Plus tard, j'ai lu un petit code emlog et j'ai découvert que le véritable code frontal du site Web se trouve dans le répertoire des modèles et qu'il n'y a que des fichiers de point d'entrée et des fichiers de configuration dans le répertoire racine. Ensuite, j’ai eu une révélation et j’ai modifié la structure de l’ensemble du site.
Placez un fichier de point d'entrée dans le répertoire racine du site Web et laissez-le gérer toutes les pages de l'ensemble du site Web. A ce moment, la page d'inscription devient http://localhost/?act=register
N'importe quelle page n'est qu'un paramètre d'acte. Après avoir obtenu ce paramètre Enfin, utilisez un commutateur pour sélectionner le contenu du fichier à inclure. Ce fichier de point d'entrée peut également contenir la définition de certaines constantes, telles que le chemin absolu du site Web, l'adresse du site Web et le mot de passe de l'utilisateur de la base de données. À l'avenir, lors de l'écriture de scripts, nous essaierons d'utiliser des chemins absolus au lieu de chemins relatifs (sinon, si le script change d'emplacement, le code changera également), et ce chemin absolu provient de la définition dans le fichier de point d'entrée.
Bien entendu, en termes de sécurité, un fichier de point d'entrée peut également masquer l'adresse du backend. Des adresses comme celle-ci http://localhost/?act=xxx
n'exposent pas le chemin absolu d'arrière-plan et peuvent même être modifiées fréquemment sans trop changer de code. Un fichier de point d'entrée peut également vérifier l'identité du visiteur, comme le backend d'un site Web, où les non-administrateurs ne sont autorisés à visualiser aucune page. Vous pouvez vérifier votre identité dans le fichier du point d'entrée. Si vous n'êtes pas connecté, une page 404 sera affichée.
Avec le fichier de point d'entrée, j'ai ajouté cette phrase devant tous les fichiers sans point d'entrée :
<?php if(!defined('WWW_ROOT')) { header("HTTP/1.1 404 Not Found"); exit; } ?>
WWW_ROOT est une constante que j'ai définie dans le point d'entrée si l'utilisateur accède via. le chemin absolu de cette page (http://localhost/register.php
) générera une erreur 404 ; seul l'accès via le point d'entrée (http://localhost/?act=register
) pourra exécuter le code suivant ;
2. Utilisez des instructions précompilées pour éviter l'injection SQL
L'injection était un gros problème au début, mais ces dernières années, tout le monde a prêté plus d'attention à ce problème. , donc ça s'améliore lentement.
Wu Hanqing l'a très bien dit dans Web White Hat. En fait, de nombreuses vulnérabilités, comme l'injection SQL ou XSS, ne font pas la distinction entre « données » et « code ». Le « code » est ce que le programmeur écrit et les « données » sont ce que l'utilisateur peut modifier. Si nous écrivons une instruction SQL select * from admin where username='admin' password='xxxxx'
, admin et xxxxx sont les données, qui sont le nom d'utilisateur et le mot de passe saisis par l'utilisateur. Mais s'il n'y a pas de traitement, l'entrée de l'utilisateur peut être un "code", tel que "ou". =', ce qui a créé une faille. Le « code » ne doit jamais être accessible aux utilisateurs.
En php, il existe deux modules pour la base de données mysql, mysql et mysqli Mysqli signifie mysql améliorer. Version améliorée de mysql, ce module contient la notion de "précompilation". Comme l'instruction SQL ci-dessus, remplacez-la par : select * from admin where username='?' password='?'
, ce n'est pas une instruction SQL, mais elle peut être compilée en un objet stmt via la fonction de pré-compilation de mysqli. Plus tard, une fois que l'utilisateur a entré le mot de passe du compte, utilisez. stmt-> ;bind_param lie les "données" saisies par l'utilisateur aux positions de ces deux points d'interrogation. De cette manière, le contenu saisi par l'utilisateur ne peut être que des « données » et ne peut être transformé en « code ».
Ces deux points d'interrogation définissent l'emplacement des "données" et la structure de l'instruction SQL. Nous pouvons encapsuler toutes nos opérations de base de données dans une classe et l'exécution de toutes les instructions SQL est précompilée. Cela évite complètement l'injection SQL, qui est également la solution la plus recommandée par Wu Hanqing.
Voici quelques parties de code utilisant mysqli (j'ai omis tous les codes qui déterminent si la fonction s'exécute avec succès ou a échoué, mais cela ne veut pas dire que ce n'est pas important) :
<?php //用户输入的数据 $name = 'admin'; $pass = '123456'; //首先新建mysqli对象,构造函数参数中包含了数据库相关内容。 $conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT); //设置sql语句默认编码 $this->mysqli->set_charset("utf8"); //创建一个使用通配符的sql语句 $sql = 'SELECT user_id FROM admin WHERE username=? AND password=?;'; //编译该语句,得到一个stmt对象. $stmt = $conn->prepare($sql); /********************之后的内容就能重复利用,不用再次编译*************************/ //用bind_param方法绑定数据 //大家可以看出来,因为我留了两个?,也就是要向其中绑定两个数据,所以第一个参数是绑定的数据的类型(s=string,i=integer),第二个以后的参数是要绑定的数据 $stmt->bind_param('ss', $name, $pass); //调用bind_param方法绑定结果(如果只是检查该用户与密码是否存在,或只是一个DML语句的时候,不用绑定结果) //这个结果就是我select到的字段,有几个就要绑定几个 $stmt->bind_result($user_id); //执行该语句 $stmt->execute(); //得到结果 if($stmt->fetch()){ echo '登陆成功'; //一定要注意释放结果资源,否则后面会出错 $stmt->free_result(); return $user_id; //返回刚才select到的内容 }else{echo '登录失败';} ?>
3. Code XSS de prévention, si vous n'avez pas besoin d'utiliser des cookies, ne l'utilisez pas
Je n'utilise pas de cookies sur mon site Web, et parce que je restreint très étroitement les autorisations , le risque de XSS est relativement faible.
Pour la défense de xss, le même principe s'applique. Gérez bien la relation entre « code » et « données ». Bien entendu, le code ici fait référence au code javascript ou au code html. Pour le contenu qui peut être contrôlé par l'utilisateur, nous devons utiliser des fonctions telles que htmlspecialchars pour traiter les données saisies par l'utilisateur, et veiller à afficher le contenu sur la page en JavaScript.
4. Restreindre les autorisations des utilisateurs et empêcher CSRF
现在脚本漏洞比较火的就是越权行为,很多重要操作使用GET方式执行,或使用POST方式执行而没有核实执行者是否知情。
CSRF很多同学可能比较陌生,其实举一个小例子就行了:
A、B都是某论坛用户,该论坛允许用户“赞”某篇文章,用户点“赞”其实是访问了这个页面:http://localhost/?act=support&articleid=12
。这个时候,B如果把这个URL发送给A,A在不知情的情况下打开了它,等于说给articleid=12的文章赞了一次。
所以该论坛换了种方式,通过POST方式来赞某篇文章。
<form action="http://localhost/?act=support" method="POST"> <input type="hidden" value="12" name="articleid"> <input type="submit" value="赞"> </form>
可以看到一个隐藏的input框里含有该文章的ID,这样就不能通过一个URL让A点击了。但是B可以做一个“极具诱惑力”的页面,其中某个按钮就写成这样一个表单,来诱惑A点击。A一点击,依旧还是赞了这篇文章。
最后,该论坛只好把表单中增加了一个验证码。只有A输入验证码才能点赞。这样,彻底死了B的心。
但是,你见过哪个论坛点“赞”也要输入验证码?
所以吴翰清在白帽子里也推荐了最好的方式,就是在表单中加入一个随机字符串token(由php生成,并保存在SESSION中),如果用户提交的这个随机字符串和SESSION中保存的字符串一致,才能赞。
在B不知道A的随机字符串时,就不能越权操作了。
我在网站中也多次使用了TOKEN,不管是GET方式还是POST方式,通常就能抵御99%的CSRF估计了。
5、严格控制上传文件类型
上传漏洞是很致命的漏洞,只要存在任意文件上传漏洞,就能执行任意代码,拿到webshell。
我在上传这部分,写了一个php类,通过白名单验证,来控制用户上传恶意文件。在客户端,我通过javascript先验证了用户选择的文件的类型,但这只是善意地提醒用户,最终验证部分,还是在服务端。
白名单是必要的,你如果只允许上传图片,就设置成array('jpg','gif','png','bmp'),当用户上传来文件后,取它的文件名的后缀,用in_array验证是否在白名单中。
在上传文件数组中,会有一个MIME类型,告诉服务端上传的文件类型是什么,但是它是不可靠的,是可以被修改的。在很多存在上传漏洞的网站中,都是只验证了MIME类型,而没有取文件名的后缀验证,导致上传任意文件。
所以我们在类中完全可以忽略这个MIME类型,而只取文件名的后缀,如果在白名单中,才允许上传。
当然,服务器的解析漏洞也是很多上传漏洞的突破点,所以我们尽量把上传的文件重命名,以“日期时间+随机数+白名单中后缀”的方式对上传的文件进行重命名,避免因为解析漏洞而造成任意代码执行。
6、加密混淆javascript代码,提高攻击门槛
很多xss漏洞,都是黑客通过阅读javascript代码发现的,如果我们能把所有javascript代码混淆以及加密,让代码就算解密后也是混乱的(比如把所有变量名替换成其MD5 hash值),提高阅读的难度。
7、使用更高级的hash算法保存数据库中重要信息
这个硬盘容量大增的时期,很多人拥有很大的彩虹表,再加上类似于cmd5这样的网站的大行其道,单纯的md5已经等同于无物,所以我们迫切的需要更高级的hash算法,来保存我们数据库中的密码。
所以后来出现了加salt的md5,比如discuz的密码就是加了salt。其实salt就是一个密码的“附加值”,比如A的密码是123456,而我们设置的salt是abc,这样保存到数据库的可能就是md5('123456abc'),增加了破解的难度。
但是黑客只要得知了该用户的salt也能跑md5跑出来。因为现在的计算机的计算速度已经非常快了,一秒可以计算10亿次md5值,弱一点的密码分把钟就能跑出来。
所以后来密码学上改进了hash,引进了一个概念:密钥延伸。说简单点就是增加计算hash的难度(比如把密码用md5()函数循环计算1000次),故意减慢计算hash所用的时间,以前一秒可以计算10亿次,改进后1秒只能计算100万次,速度慢了1000倍,这样,所需的时间也就增加了1000倍。
那么对于我们,怎么使用一个安全的hash计算方法?大家可以翻阅emlog的源码,可以在include目录里面找到一个HashPaaword.php的文件,其实这就是个类,emlog用它来计算密码的hash。
Cette classe a une fonctionnalité. La valeur de hachage calculée est différente à chaque fois, de sorte que les pirates ne peuvent pas déchiffrer le mot de passe via les tables arc-en-ciel et d'autres méthodes. Ils ne peuvent utiliser qu'une méthode de vérification du mot de passe dans cette classe pour renvoyer l'exactitude du mot de passe saisi. l'utilisateur. Et cette fonction augmente délibérément le temps de calcul du hachage, il est donc difficile pour les pirates de déchiffrer la valeur de hachage qu'ils obtiennent.
Dans le dernier php5.5, cet algorithme de hachage est devenu une fonction formelle, et nous pouvons utiliser cette fonction pour hacher nos mots de passe à l'avenir
8. 🎜>
Le code de vérification est généralement une chaîne aléatoire générée par un script php, qui est traitée par la bibliothèque GD et transformée en image. La véritable chaîne de code de vérification est stockée dans SESSION, puis l'image générée est affichée à l'utilisateur. Une fois que l'utilisateur a rempli le code de vérification et l'a soumis, le code de vérification de la SESSION est comparé sur le serveur. Cela me rappelle une erreur que j'ai commise auparavant. Une fois la comparaison du code de vérification terminée, qu'il soit correct ou faux, je n'ai pas effacé la SESSION. Cela crée un problème. Une fois qu'un utilisateur soumet avec succès le code de vérification pour la première fois, il n'accédera plus au script qui génère le code de vérification après la deuxième fois. À ce stade, le code de vérification dans la SESSION n'est pas mis à jour ou supprimé. , entraînant la réutilisation du code de vérification, ne répond pas à l'objectif de vérification. Parlons du problème de la reconnaissance des codes de vérification. J'utilise souvent des programmes WordPress, notamment emlog, pour référence, mais je ne peux pas faire l'éloge des codes de vérification qu'ils utilisent. De nombreux commentaires de spam ont été générés après que le code de vérification a été reconnu par la machine, j'ai donc utilisé plus tard un code de vérification plus complexe, qui serait recommandé par le w3c. D'accord, il n'y a qu'un nombre limité de choses auxquelles je peux penser et qui sont utilisées dans des applications pratiques. Ce ne sont là que quelques-unes de mes propres connaissances sur la sécurité du code que j'ai accumulées en écrivant mon propre code. Si vous avez de meilleures idées, vous pouvez communiquer avec moi. J'espère que tout le monde pourra également écrire du code plus sûr. Le contenu ci-dessus est à titre de référence uniquement ! Tutoriel vidéo recommandé :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!