首頁  >  文章  >  後端開發  >  開發中php安全性考慮哪些

開發中php安全性考慮哪些

王林
王林轉載
2019-09-09 18:00:443530瀏覽

開發中php安全性考慮哪些

開發中php安全性要考慮哪些?

1掌握整站的結構,避免洩漏網站敏感目錄

在寫程式碼之初,我也是像很多舊原始碼一樣,在根目錄下放上index.php、register.php、login.php,使用者點選註冊頁面,就跳到http://localhost/register.php。並沒有太多的結構的思想,像這樣的程式碼結構,最大的問題倒不是安全性問題,而是程式碼擴展與移植問題。

在寫程式碼的過程中,我們常要對程式碼進行修改,這時候如果程式碼沒有統一的一個入口點,我們可能要改很多地方。後來我讀了一點emlog的程式碼,發現網站真正的前端程式碼都在模板目錄裡,而根目錄下就只有入口點檔案和設定檔。這才頓悟,整個網站的架構進行了修改。

網站根目錄下放上一個入口點文件,讓它來對整個網站所有頁面進行管理,這個時候註冊頁面變成了http://localhost/?act=register ,任何頁面只是act的一個參數,得到這個參數後,再用一個switch來選擇要包含的檔案內容。在這個入口點檔案中,還可以包含一些常數的定義,例如網站的絕對路徑、網站的位址、資料庫使用者密碼。以後我們在腳本的編寫中,盡量使用絕對路徑而不要使用相對路徑(否則腳本如果改變位置,程式碼也要變),而這個絕對路徑就來自入口點檔案中的定義。

當然,在安全性上,一個入口點檔案也能隱藏後台位址。像這樣的位址http://localhost/?act=xxx不會暴露後台絕對路徑,甚至可以經常更改,不用改變太多程式碼。一個入口點檔案也可以驗證訪客的身份,例如一個網站後台,不是管理員就不允許查看任何頁面。在入口點文件中就可以驗證身份,如果沒有登錄,就輸出404頁。

有了入口點文件,我就把所有非入口點文件前面加上了這句話:

<?php 
if(!defined(&#39;WWW_ROOT&#39;))
 {
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='?',它就不是一個sql語句了,但是可以透過mysqli的預編譯功能先把他編譯成stmt對象,在後製用戶輸入帳號密碼後,用stmt->bind_param將用戶輸入的「資料」綁定到這兩個問號的位置。這樣,使用者輸入的內容就只能是“資料”,而不可能變成“代碼”。

這兩個問號限定了「資料」的位置,以及sql語句的結構。我們可以把我們所有的資料庫操作都封裝到一個類別中,所有sql語句的執行都會進行預編譯。這樣就完全避免了sql注入,這也是吳翰清最推薦的解決方案。

下面是使用mysqli的一些程式碼部分(所有的判斷函數運行成功或失敗的程式碼我都省略了,但不代表不重要):

<?php
//用户输入的数据
$name = &#39;admin&#39;;
$pass = &#39;123456&#39;;
//首先新建mysqli对象,构造函数参数中包含了数据库相关内容。
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT);
//设置sql语句默认编码
$this->mysqli->set_charset("utf8");
//创建一个使用通配符的sql语句
$sql = &#39;SELECT user_id FROM admin WHERE username=? AND password=?;&#39;;
//编译该语句,得到一个stmt对象.
$stmt = $conn->prepare($sql);
/********************之后的内容就能重复利用,不用再次编译*************************/
//用bind_param方法绑定数据
//大家可以看出来,因为我留了两个?,也就是要向其中绑定两个数据,所以第一个参数是绑定的数据的类型(s=string,i=integer),第二个以后的参数是要绑定的数据
$stmt->bind_param(&#39;ss&#39;, $name, $pass);
//调用bind_param方法绑定结果(如果只是检查该用户与密码是否存在,或只是一个DML语句的时候,不用绑定结果)
//这个结果就是我select到的字段,有几个就要绑定几个
$stmt->bind_result($user_id);
//执行该语句
$stmt->execute();
//得到结果
if($stmt->fetch()){
 echo &#39;登陆成功&#39;;
 //一定要注意释放结果资源,否则后面会出错
 $stmt->free_result();
 return $user_id; //返回刚才select到的内容
}else{echo &#39;登录失败&#39;;}
?>

3、預防XSS程式碼,如果不需要使用cookie就不使用

在我的網站中並沒有使用cookie,更因為我對權限限制的很死,所以對於xss來說危險性比較小。

對於xss的防禦,也是一個道理,處理好「程式碼」和「資料」的關係。當然,這裡的程式碼指的就是javascript程式碼或html程式碼。使用者能控制的內容,我們一定要使用htmlspecialchars等函數來處理使用者輸入的數據,並且在javascript中要謹慎把內容輸出到頁面中。

4、限制使用者權限,預防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。

這個類別有一個特點,每次計算出的hash值都不一樣,所以駭客不能透過彩虹表等方式破解密碼,只能用這個類別中一個checkpassword方法來傳回使用者輸入密碼的正確性。而函數又刻意增加了計算hash的時間,所以駭客很難破解他們拿到的hash值。

在最新的php5.5中,這個hash演算法成為了一個正式的函數,以後就能使用該函數來hash我們的密碼了

8、驗證碼安全性

驗證碼通常是由php腳本產生的隨機字串,透過GD庫的處理,製作成圖片。真正的驗證碼字串保存在SESSION中,然後把產生的圖片展示給使用者。使用者填寫了驗證碼提交後,在服務端上SESSION中的驗證碼進行比對。

由此想到了我之前犯過的一個錯誤。驗證碼比對完成之後,不管是正確還是錯誤,我都沒有清理SESSION。這樣產生了一個問題,一旦一個用戶第一次提交驗證碼成功,第二次以後不再訪問生成驗證碼的腳本,這時候SESSION中的驗證碼並沒有更新,也沒有刪除,導致驗證碼重複使用,起不到驗證的作用。

再說到了驗證碼被辨識的問題,wordpress包含emlog的程式我常常會藉鑑,但他們所使用的驗證碼我卻不敢恭維。很多垃圾評論都是驗證碼被機器辨識後產生的,所以我後來也使用了一個複雜一點的驗證碼,據說是w3c推薦使用的。

好了,我能想到的,也是在實際運用上用到的東西也就這麼多了。這也只是我自己寫程式碼中累積的一些對程式碼安全性的一個見解,如果大家還有更好的想法,可以和我交流。希望大家也能寫出更安全的程式碼。

以上內容僅供參考!

推薦影片教學:PHP影片教學

#

以上是開發中php安全性考慮哪些的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:jb51.net。如有侵權,請聯絡admin@php.cn刪除