PHP 보안을 개발할 때 무엇을 고려해야 합니까?
1, 사이트의 민감한 디렉토리가 유출되지 않도록 전체 사이트의 구조를 파악하세요
코드 작성 초반에는 많은 예전 소스코드처럼 index.php,register.php, login을 넣었습니다. 루트 디렉토리에서.php, 사용자가 등록 페이지를 클릭하면 http://localhost/register.php
로 이동합니다. 이런 코드 구조에서는 구조적인 생각이 별로 없고, 가장 큰 문제는 보안이 아니라 코드 확장과 이식입니다. http://localhost/register.php
。并没有太多的结构的思想,像这样的代码结构,最大的问题倒不是安全性问题,而是代码扩展与移植问题。
在写代码的过程中,我们常要对代码进行修改,这时候如果代码没有统一的一个入口点,我们可能要改很多地方。后来我读了一点emlog的代码,发现网站真正的前端代码都在模板目录里,而根目录下就只有入口点文件和配置文件。这才顿悟,对整个网站的结构进行了修改。
网站根目录下放上一个入口点文件,让它来对整个网站所有页面进行管理,这个时候注册页面变成了http://localhost/?act=register
,任何页面只是act的一个参数,在得到这个参数后,再用一个switch来选择要包含的文件内容。在这个入口点文件中,还可以包含一些常量的定义,比如网站的绝对路径、网站的地址、数据库用户密码。以后我们在脚本的编写中,尽量使用绝对路径而不要使用相对路径(否则脚本如果改变位置,代码也要变),而这个绝对路径就来自入口点文件中的定义。
当然,在安全性上,一个入口点文件也能隐藏后台地址。像这样的地址http://localhost/?act=xxx
不会暴露后台绝对路径,甚至可以经常更改,不用改变太多代码。一个入口点文件也可以验证访问者的身份,比如一个网站后台,不是管理员就不允许查看任何页面。在入口点文件中就可以验证身份,如果没有登录,就输出404页面。
有了入口点文件,我就把所有非入口点文件前面加上了这句话:
<?php if(!defined('WWW_ROOT')) { header("HTTP/1.1 404 Not Found"); exit; } ?>
WWW_ROOT是我在入口点中定义的一个常量,如果用户是通过这个页面的绝对路径访问(http://localhost/register.php
),我就输出404错误;只有通过入口点访问(http://localhost/?act=register
),才能执行后面的代码。
2、使用预编译语句,避免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='?'
http://localhost/?act=register
가 됩니다. , 어떤 페이지든 act의 매개변수일 뿐입니다. 이 매개변수를 가져온 후 스위치를 사용하여 포함할 파일 내용을 선택하세요. 이 진입점 파일에는 웹 사이트의 절대 경로, 웹 사이트 주소 및 데이터베이스 사용자 비밀번호와 같은 일부 상수의 정의가 포함될 수도 있습니다. 앞으로는 스크립트를 작성할 때 상대 경로 대신 절대 경로를 사용하려고 노력할 것이며(그렇지 않으면 스크립트 위치가 변경되면 코드도 변경됩니다), 이 절대 경로는 진입점 파일의 정의에서 나옵니다. 물론 보안 측면에서 진입점 파일은 백엔드 주소를 숨길 수도 있습니다. http://localhost/?act=xxx
와 같은 주소는 백그라운드에서 절대 경로를 노출하지 않으며, 코드를 너무 많이 변경하지 않고도 자주 변경할 수도 있습니다. 진입점 파일은 관리자가 아닌 사람이 어떤 페이지도 볼 수 없는 웹사이트 백엔드와 같이 방문자의 신원을 확인할 수도 있습니다. 로그인하지 않은 경우 진입점 파일에서 신원을 확인할 수 있습니다. 404 페이지가 출력됩니다. 진입점 파일에서 모든 비진입점 파일 앞에 다음 문장을 추가했습니다.
<?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 '登录失败';} ?>
http://localhost/register.php
), 진입점(http://localhost/?act=register
)을 통해서만 액세스하면 404 오류가 출력됩니다. 다음 코드를 실행할 수 있습니다. 2. SQL 인젝션을 피하기 위해 준비된 문장을 사용하세요
Injection은 초기에는 큰 문제였지만 최근에는 모두가 이 문제에 더 많은 관심을 기울이기 때문에 점차 많이 좋아졌습니다.
Wu Hanqing은 Web White Hat에서 이를 아주 잘 말했습니다. 실제로 SQL 삽입이나 XSS와 같은 많은 취약점은 "데이터"와 "코드"를 구분하지 않습니다. "코드"는 프로그래머가 작성하는 것이고, "데이터"는 사용자가 변경할 수 있는 것입니다. SQL 문 select * from admin where username='admin'password='xxxxx'
를 작성하면 admin과 xxxxx는 사용자가 입력한 사용자 이름과 비밀번호인 데이터이지만, 처리가 없으면 사용자 입력은 취약점을 생성하는 '또는 ''='와 같은 "코드"일 수 있습니다. "코드"는 사용자가 절대 접근할 수 없어야 합니다.
select * from admin where username='?'password='?'
, 이는 SQL 문은 아니지만 mysqli의 사전 컴파일 기능을 사용할 수 있습니다. 이를 stmt 객체로 컴파일합니다. 사용자가 나중에 계정과 비밀번호를 입력한 후 stmt->bind_param을 사용하여 사용자가 입력한 "데이터"를 이 두 물음표 위치에 바인딩합니다. 이런 방식으로 사용자가 입력한 내용은 '데이터'일 뿐이고 '코드'로 변환될 수는 없습니다. 🎜🎜이 두 물음표는 "데이터"의 위치와 SQL 문의 구조를 정의합니다. 모든 데이터베이스 작업을 클래스로 캡슐화할 수 있으며 모든 SQL 문의 실행이 미리 컴파일됩니다. 이는 Wu Hanqing이 가장 권장하는 솔루션이기도 한 SQL 주입을 완전히 방지합니다. 🎜🎜다음은 mysqli를 사용하는 일부 코드 부분입니다(함수 실행 성공 여부를 결정하는 코드는 모두 생략했지만 중요하지 않다는 의미는 아닙니다). 🎜<form action="http://localhost/?act=support" method="POST"> <input type="hidden" value="12" name="articleid"> <input type="submit" value="赞"> </form>
现在脚本漏洞比较火的就是越权行为,很多重要操作使用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。
이 클래스의 특징은 계산된 해시 값이 매번 다르기 때문에 해커는 레인보우 테이블 및 기타 방법을 통해 비밀번호를 해독할 수 없다는 것입니다. 이 클래스에서는 사용자가 입력한 비밀번호의 정확성을 반환하기 위해 checkpassword 방법만 사용할 수 있습니다. . 그리고 이 기능은 의도적으로 해시를 계산하는 시간을 늘리므로 해커가 얻은 해시 값을 해독하기가 어렵습니다.
최신 php5.5에서는 이 해시 알고리즘이 정식 기능이 되었으며 앞으로 이 기능을 사용하여 비밀번호를 해시할 수 있습니다
8. 인증 코드 보안
인증 코드는 일반적으로 다음에서 생성됩니다. PHP 스크립트에 의해 생성된 임의의 문자열은 GD 라이브러리에 의해 처리되어 이미지로 만들어집니다. 실제 인증코드 문자열은 SESSION에 저장되며, 생성된 이미지는 사용자에게 표시됩니다. 사용자가 인증 코드를 입력하고 제출하면 서버에서 SESSION의 인증 코드를 비교합니다.
이거 보니 예전에 했던 실수가 생각나네요. 인증코드 비교가 완료된 후, 맞는지 틀린지 SESSION을 지우지 않았습니다. 이로 인해 문제가 발생합니다. 사용자가 처음으로 인증 코드를 제출하면 두 번째부터는 더 이상 인증 코드를 생성하는 스크립트에 액세스할 수 없습니다. , 인증 코드를 재사용하게 되어 인증 목적에 부합하지 않습니다.
인증코드가 인식되는 문제에 대해 이야기해보겠습니다. 저는 참고용으로 emlog를 포함한 워드프레스 프로그램을 자주 사용하지만, 인증코드를 사용하는 것은 칭찬할 수 없습니다. 인증코드를 기계가 인식한 이후에 많은 스팸 댓글이 생성되었기 때문에 나중에는 w3c에서 권장한다고 하는 좀 더 복잡한 인증코드를 사용했습니다.
그렇습니다. 실제 응용 프로그램에 사용되는 것 중 제가 생각할 수 있는 것은 너무 많습니다. 이것은 제가 직접 코드를 작성하면서 축적한 코드 보안에 대한 통찰력 중 일부입니다. 더 좋은 아이디어가 있으면 저에게 연락해 주세요. 모두가 더 안전한 코드를 작성할 수 있기를 바랍니다.
위 내용은 참고용입니다!
추천 비디오 튜토리얼: PHP 비디오 튜토리얼
위 내용은 PHP 보안을 개발할 때 무엇을 고려해야 합니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!