欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  php教程

浅谈php安全性需要注意的几点事项

程序员文章站 2022-05-23 22:38:20
...

这段时间一直在写一个整站,前几天才基本完成了,所以抽个时间写了一篇对于php安全的总结。技术含量不高,过不了也没关系,希望能一些准备写网站的朋友一点引导

在放假之初,我抽时间看了《白帽子讲web安全》,吴翰清基本上把web安全中所有能够遇到的问题、解决思路归纳总结得很清晰,也是我这一次整体代码安全性的基石。

我希望能分如下几个方面来分享自己的经验

把握整站的结构,避免泄露站点敏感目录

在写代码之初,我也是像很多老源码一样,在根目录下放上index.php、register.php、login.php,用户点击注册页面,就跳转到。并没有太多的结构的思想,像这样的代码结构,最大的问题倒不是安全性问题,而是代码扩展与移植问题。

在写代码的过程中,我们常要对代码进行修改,这时候如果代码没有统一的一个入口点,我们可能要改很多地方。后来我读了一点emlog的代码,发现网站真正的前端代码都在模板目录里,而根目录下就只有入口点文件和配置文件。这才顿悟,对整个网站的结构进行了修改。

网站根目录下放上一个入口点文件,让它来对整个网站所有页面进行管理,这个时候注册页面变成了,任何页面只是act的一个参数,在得到这个参数后,再用一个switch来选择要包含的文件内容。在这个入口点文件中,还可以包含一些常量的定义,比如网站的绝对路径、网站的地址、数据库用户密码。以后我们在脚本的编写中,尽量使用绝对路径而不要使用相对路径(否则脚本如果改变位置,代码也要变),而这个绝对路径就来自入口点文件中的定义。

当然,在安全性上,一个入口点文件也能隐藏后台地址。像这样的地址不会暴露后台绝对路径,甚至可以经常更改,不用改变太多代码。一个入口点文件也可以验证访问者的身份,比如一个网站后台,不是管理员就不允许查看任何页面。在入口点文件中就可以验证身份,如果没有登录,就输出404页面。

有了入口点文件,我就把所有非入口点文件前面加上了这句话:

WWW_ROOT是我在入口点中定义的一个常量,如果用户是通过这个页面的绝对路径访问(),我就输出404错误;只有通过入口点访问(),才能执行后面的代码。

使用预编译语句,避免sql注入

注入是早前很大的一个问题,不过近些年因为大家比较重视这个问题,所以慢慢变得好了很多。

吴翰清在web白帽子里说的很好,其实很多漏洞,像sql注入或xss,都是将“数据”和“代码”没有区分开。“代码”是程序员写的内容,“数据”是用户可以改变的内容。如果我们写一个sql语句select * from admin where username='admin' password='xxxxx', admin和xxxxx就是数据,是用户输入的用户名和密码,但如果没有任何处理,用户输入的就可能是“代码”,比如'or ''=',这样就造成了漏洞。“代码”是绝对不能让用户接触的。

在php中,,对于mysql数据库有两个模块,mysql和mysqli,mysqli的意思就是mysql improve。mysql的改进版,这个模块中就含有“预编译”这个概念。像上面那个sql语句,改一改:select * from admin where username='?' password='?',它就不是一个sql语句了,但是可以通过mysqli的预编译功能先把他编译成stmt对象,在后期用户输入账号密码后,用stmt->bind_param将用户输入的“数据”绑定到这两个问号的位置。这样,用户输入的内容就只能是“数据”,而不可能变成“代码”。

这两个问号限定了“数据”的位置,以及sql语句的结构。我们可以把我们所有的数据库操作都封装到一个类中,所有sql语句的执行都进行预编译。这样就完全避免了sql注入,这也是吴翰清最推荐的解决方案。

下面是使用mysqli的一些代码部分(所有的判断函数运行成功或失败的代码我都省略了,但不代表不重要):

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 '登录失败';} ?>

预防XSS代码,如果不需要使用cookie就不使用

在我的网站中并没有使用cookie,更因为我对权限限制的很死,所以对于xss来说危险性比较小。

对于xss的防御,也是一个道理,处理好“代码”和“数据”的关系。当然,这里的代码指的就是javascript代码或html代码。用户能控制的内容,我们一定要使用htmlspecialchars等函数来处理用户输入的数据,并且在javascript中要谨慎把内容输出到页面中。

限制用户权限,预防CSRF

现在脚本漏洞比较火的就是越权行为,很多重要操作使用GET方式执行,或使用POST方式执行而没有核实执行者是否知情。

CSRF很多同学可能比较陌生,其实举一个小例子就行了:

A、B都是某论坛用户,该论坛允许用户“赞”某篇文章,用户点“赞”其实是访问了这个页面:。这个时候,B如果把这个URL发送给A,A在不知情的情况下打开了它,等于说给articleid=12的文章赞了一次。

所以该论坛换了种方式,通过POST方式来赞某篇文章。

可以看到一个隐藏的input框里含有该文章的ID,这样就不能通过一个URL让A点击了。但是B可以做一个“极具诱惑力”的页面,其中某个按钮就写成这样一个表单,来诱惑A点击。A一点击,依旧还是赞了这篇文章。

最后,该论坛只好把表单中增加了一个验证码。只有A输入验证码才能点赞。这样,彻底死了B的心。

但是,你见过哪个论坛点“赞”也要输入验证码?

所以吴翰清在白帽子里也推荐了最好的方式,就是在表单中加入一个随机字符串token(由php生成,并保存在SESSION中),如果用户提交的这个随机字符串和SESSION中保存的字符串一致,才能赞。

在B不知道A的随机字符串时,就不能越权操作了。

我在网站中也多次使用了TOKEN,不管是GET方式还是POST方式,通常就能抵御99%的CSRF估计了。

严格控制上传文件类型

上传漏洞是很致命的漏洞,只要存在任意文件上传漏洞,就能执行任意代码,拿到webshell。

我在上传这部分,写了一个php类,通过白名单验证,来控制用户上传恶意文件。在客户端,我通过javascript先验证了用户选择的文件的类型,但这只是善意地提醒用户,最终验证部分,还是在服务端。

白名单是必要的,你如果只允许上传图片,就设置成array('jpg','gif','png','bmp'),当用户上传来文件后,取它的文件名的后缀,用in_array验证是否在白名单中。