如何讓PHP的代碼更安全
概述
攻擊者通過(guò)構(gòu)造惡意SQL命令發(fā)送到數(shù)據(jù)庫(kù),如果程序未對(duì)用戶輸入的 SQL命令執(zhí)行判斷過(guò)濾,那么生成的SQL語(yǔ)句可能會(huì)繞過(guò)安全性檢查,插入其他用于修改后端數(shù)據(jù)庫(kù)的語(yǔ)句,并可能執(zhí)行系統(tǒng)命令,從而對(duì)系統(tǒng)造成危害
例如刪除 id 為 1 的帖子,sql 如下:
$post_id = $_POST['post_id']; $sql = "DELETE FROM posts WHERE user_id = 1 AND id = $post_id"; \DB::statement($sql);
如果有人在提交 post_id 時(shí)輸入 1 OR 1 ,你的語(yǔ)句會(huì)組合成這樣:
$sql = "DELETE FROM posts WHERE user_id = 1 AND id = 1 OR 1";
一般比較常出現(xiàn)在原生的 SQL 操作,框架一般會(huì)解決這方面的問(wèn)題。通常采用參數(shù)控制或過(guò)濾特殊字符避免上述的問(wèn)題。
越權(quán)漏洞
1. 水平越權(quán)
水平越權(quán)就是同等角色下的用戶,不但能夠訪問(wèn)和操作自己私有的數(shù)據(jù),還能訪問(wèn)其他人私有的數(shù)據(jù),其根本是基于數(shù)據(jù)的訪問(wèn)權(quán)限。
刪除用戶收款方式的場(chǎng)景如下:
graph LR
用戶登錄-->token
token-->獲取收款方式列表
獲取收款方式列表--token-->通過(guò)id刪除
通過(guò)收款方式 {id} 執(zhí)行 delete 請(qǐng)求的路由為:localhost/api/payments/{id}
假如用戶A的收款方式有{1,2,3} 用戶B的收款方式有{4,5}
如果沒(méi)有做數(shù)據(jù)控制,A 登錄后攜帶 A 的 token 執(zhí)行刪除的接口localhost/api/payments/4,則會(huì)刪除 B 的,所以需要對(duì)destory方法做數(shù)據(jù)控制
# 1 刪除前鑒權(quán)處理 public function destory($id){ $payment = Payment::find($id); if ($payment->user_id != $this->currentUser->id) { return ... } $payment->delete(); } # 2 參入id查詢刪除 public function destory($id){ Payment::whereUserId($this->currentUser->id)->whereId($id)->delete(); } # 3 模型關(guān)聯(lián)查詢 class User extends Model{ public function payments() { return $this->hasMany('App\Payment'); } } class PaymentController extends Controller{ public function destory($id) { $this->currentUser->payments()->whereId($id)->delete(); } }
推薦使用第三種方式做數(shù)據(jù)控制,不然面向?qū)ο蟀讓W(xué)了。獲取收款方式的列表同樣需要數(shù)據(jù)權(quán)限控制,用戶和收款方式存在一對(duì)多的關(guān)聯(lián)關(guān)系,模型關(guān)聯(lián)后,獲取用戶收款方式列表可以寫(xiě)成
class PaymentController extends Controller{ public function index($id) { #帶條件的查詢 $payments = $this->currentUser->payments()->where(function($query){ ... })->get(); #不帶條件的查詢 $payments = $this->currentUser->payments; } }
2. 垂直越權(quán)
低權(quán)限的角色通過(guò)一些途徑,獲得高權(quán)限的能力,就發(fā)生了越權(quán)訪問(wèn)。如普通用戶 guest 修改 admin 用戶的密碼;guest 可直接進(jìn)入后臺(tái)取得域名管理、用戶管理等所有權(quán)限
解決這個(gè)問(wèn)題,需要把權(quán)限職責(zé)以最小顆粒細(xì)分,基于 RBAC 設(shè)計(jì)權(quán)限管理系統(tǒng)。分為以下關(guān)聯(lián)模型
graph LR
用戶--多對(duì)多-->角色
用戶--多對(duì)多-->權(quán)限
角色--多對(duì)多-->權(quán)限
每次執(zhí)行請(qǐng)求時(shí),在前置中間件判斷這個(gè)用戶是否永遠(yuǎn)該執(zhí)行請(qǐng)求的權(quán)限,無(wú)權(quán)限則駁回。
3. 上下文越權(quán)
攻擊者能夠利用應(yīng)用程序狀態(tài)機(jī)中的漏洞獲得關(guān)鍵資源的訪問(wèn)權(quán)限,這就存在上下文相關(guān)的越權(quán)。上下文相關(guān)的越權(quán)漏洞一般屬于業(yè)務(wù)邏輯漏洞。 如在找回密碼過(guò)程中,攻擊者使用自己的賬戶信息通過(guò)驗(yàn)證,將他人的密碼進(jìn)行了修改。
graph LR
1.郵箱驗(yàn)證-->2.找回密碼
在步驟1之后,執(zhí)行找回密碼,路由為 。如果此時(shí)沒(méi)有校驗(yàn)當(dāng)前找回密碼的賬戶是否為進(jìn)行郵箱校驗(yàn)后的賬戶,由可能產(chǎn)生越權(quán)漏洞.
路由 : 【PUT 】localhost/api/users/find-password,接收參數(shù) email,new_password.
錯(cuò)誤:校驗(yàn)和修改分成 2 步localhost/api/email/check->localhost/api/users/password
class UserController extends Controller{ public function check($data) { if (checkEmail($data['email'], $data['code'])) { return true; } ... } public function findPassword() { $user = User::whereEmail($data['email'])->first(); $user->password = bcrypt($data['new_password']); $user->save(); } }
正確:在 findPassword 里面再次驗(yàn)證完成郵箱校驗(yàn)的賬戶是否為當(dāng)前找回密碼的賬號(hào)
class UserController extends Controller{ public function check($data) { if (checkEmail($data['email'], $data['code'])) { return true; } ... } public function findPassword($data) { if (checkEmail($data['email'], $data['code'])) { $user = User::whereEmail($data['email'])->first(); $user->password = $data['new_password']; $user->save(); } ... } }
限制分頁(yè)條目范圍,防止惡意請(qǐng)求
如獲文章列表的接口localhost/api/articles
public function index($params){ $pageId = $params['pageid'] ?? PAGE_ID; //頁(yè)碼 $pageSize = $params['pagesize'] ?? 15; //條碼 $articles = Article::where(function ($query) use ($params) { ... })->take($pageSize)->skip($pageId * $pageSize)->orderby('id', 'desc')->get(); ... ... }
以上代碼如果沒(méi)有限制 pagesize 的范圍,惡意請(qǐng)求者請(qǐng)求把 pagesize 輸入 5000,10000 等甚至更大的數(shù),會(huì)給數(shù)據(jù)庫(kù)帶來(lái)一定的壓力,localhost/api/articles?pageid=0&pagesize=10000
//用框架自帶的分頁(yè)方法 public function index(){ $builder = Article::with('category:id,name')->orderBy('id', 'desc')->paginate(8); return response()->json(['status' => true, 'count' => $builder->total(), 'articles' => $builder->items()]); }
JWT 的 Token 需要二次加密
許多拓展包加密出的token并不十分安全,用base64_decode可以解密獲取 加密主鍵、載荷等重要信息,所以通常需要對(duì)JWT的token進(jìn)行二次加密
限制上傳文件的類(lèi)型
對(duì)于一個(gè)圖片上傳的接口,如果沒(méi)有對(duì)上傳文件的格式做限制,攻擊者很有可能把 .php后綴的文件上傳到public/images目錄下,然后通過(guò)根目錄執(zhí)行這個(gè)文件。
需要設(shè)計(jì)安全的文件上傳功能避免上述問(wèn)題
1.文件上傳的目錄設(shè)置為不可執(zhí)行
2.判斷文件類(lèi)型
3.使用隨機(jī)數(shù)改寫(xiě)文件名和文件路徑
4.單獨(dú)設(shè)置文件服務(wù)器的域名
禁止或者避免寫(xiě)自動(dòng)解壓.zip 等壓縮文件的代碼
單純地限制文件或壓縮包大小并沒(méi)有用,一個(gè)ZIP炸彈的.zip文件僅有 42 KB,但在解壓后會(huì)占用 4718592 GB
避免登錄密碼被暴力破解
1.設(shè)定嚴(yán)格的速率限制,如登錄次數(shù)限制,登錄錯(cuò)誤次數(shù)達(dá) x 次時(shí)暫停登錄 n 分鐘
2.密碼加上隨機(jī)鹽
public function reg(){ $user = new User; $salt = radom(6); $user->password = bcrypt($data['password'] . $salt); ... }
做好異常處理,避免在生產(chǎn)環(huán)境中不正確的錯(cuò)誤報(bào)告暴露敏感數(shù)據(jù)
如果你不小心,可能會(huì)在生產(chǎn)環(huán)境中因?yàn)椴徽_的錯(cuò)誤報(bào)告泄露了敏感信息,例如:文件夾結(jié)構(gòu)、數(shù)據(jù)庫(kù)結(jié)構(gòu)、連接信息與用戶信息。
1.在.env 文件中關(guān)閉調(diào)試模式
APP_DEBUG=true
2.php 錯(cuò)誤控制 error_reporting、display_errors
<?php // 關(guān)閉錯(cuò)誤報(bào)告 error_reporting(0); // 報(bào)告 runtime 錯(cuò)誤 error_reporting(E_ERROR | E_WARNING | E_PARSE); // 報(bào)告所有錯(cuò)誤 error_reporting(E_ALL); // 等同 error_reporting(E_ALL); ini_set("error_reporting", E_ALL); // 報(bào)告 E_NOTICE 之外的所有錯(cuò)誤 error_reporting(E_ALL & ~E_NOTICE); ?> display_errors = Off
php 弱語(yǔ)言的設(shè)計(jì)缺陷如:in_array
$array=[0,1,2,'3']; var_dump(in_array('abc', $array)); //true var_dump(in_array('1bc', $array)); //true # 上面的情況返回的都是 true, 因?yàn)?abc'會(huì)轉(zhuǎn)換為 0,'1bc'轉(zhuǎn)換為 1 $a = null; $b = false; echo $a==$b; //true $c = ""; $d = 0; echo $c==$d //true
在一些重要的地方需要使用===來(lái)作數(shù)據(jù)判斷。
LFI (本地文件包含)
LFI (本地文件包含) 是一個(gè)用戶未經(jīng)驗(yàn)證從磁盤(pán)讀取文件的漏洞。
不驗(yàn)證過(guò)濾用戶的輸入 將它要渲染的模板文件用 GET 請(qǐng)求加載。
<body> <?php $page = $_GET['page']; if(!$page) { $page = 'main.php'; } include($page); ?></body>
由于 Include 可以加載任何文件,不僅僅是 PHP,攻擊者可以將系統(tǒng)上的任何文件作為包含目標(biāo)傳遞。
index.php?page=../../etc/passwd
這將導(dǎo)致 /etc/passwd 文件被讀取并展示在瀏覽器上。
要防御此類(lèi)攻擊,你必須仔細(xì)考慮允許用戶輸入的類(lèi)型,并刪除可能有害的字符,如輸入字符中的 “.” “/” “\”。
XSS
XSS 又叫 CSS (Cross Site Script) ,跨站腳本攻擊。它指的是惡意攻擊者往 Web 頁(yè)面里插入惡意 html 代碼,當(dāng)用戶瀏覽該頁(yè)之時(shí),嵌入其中 Web 里面的 html 代碼會(huì)被執(zhí)行,從而達(dá)到惡意攻擊用戶的特殊目的。
<body> <?php $searchQuery = $_GET['q']; /* some search magic here */ ?><h1>You searched for: <?php echo $searchQuery; ?></h1></body>
因?yàn)槲覀儼延脩舻膬?nèi)容直接打印出來(lái),不經(jīng)過(guò)任何過(guò)濾,非法用戶可以拼接 URL: search.php?q=%3Cscript%3Ealert(1)%3B%3C%2Fscript%3E
PHP 渲染出來(lái)的內(nèi)容如下,可以看到 Javascript 代碼會(huì)被直接執(zhí)行:
<body> <h1>You searched for: <script>alert(1);</script></h1> <p>We found: Absolutely nothing because this is a demo</p> </body>
Javascript 可以:
- 偷走你用戶瀏覽器里的 Cookie;
- 通過(guò)瀏覽器的記住密碼功能獲取到你的站點(diǎn)登錄賬號(hào)和密碼;
- 盜取用戶的機(jī)密信息;
- 你的用戶在站點(diǎn)上能做到的事情,有了 JS 權(quán)限執(zhí)行權(quán)限就都能做,也就是說(shuō) A 用戶可以模擬成為任何用戶;
- 在你的網(wǎng)頁(yè)中嵌入惡意代碼;
使用htmlentities()過(guò)濾特殊字符,防止大部分的 xss 攻擊
CSRF (跨站請(qǐng)求偽造)
例如網(wǎng)站上有用戶可以用來(lái)注銷(xiāo)賬戶的鏈接。
<a rel="external nofollow" >銷(xiāo)毀賬戶</a>
如果某個(gè)用戶評(píng)論:
<img src=”http://your-website.com/delete-account”> wow
用戶將在查看此評(píng)論的時(shí)候刪除他們的賬號(hào)。
laravel 的 web 路由默認(rèn)開(kāi)啟了 csrf 驗(yàn)證,原理是在客戶端產(chǎn)生一個(gè)隨機(jī)的 token,在表單校驗(yàn)時(shí)判斷這個(gè) token 是否是這個(gè)頁(yè)面上的請(qǐng)求
以上就是如何讓PHP的代碼更安全的詳細(xì)內(nèi)容,更多關(guān)于讓PHP的代碼更安全的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
php實(shí)現(xiàn)圖片以base64顯示的方法
這篇文章主要介紹了php實(shí)現(xiàn)圖片以base64顯示的方法,較為詳細(xì)的分析了base64編碼格式顯示圖片的相關(guān)實(shí)現(xiàn)技巧與注意事項(xiàng),需要的朋友可以參考下2016-10-10php去掉URL網(wǎng)址中帶有PHPSESSID的配置方法
這篇文章主要介紹了php開(kāi)發(fā)的網(wǎng)站網(wǎng)址中帶有PHPSESSID的解決辦法,也就是把PHP的跨頁(yè)傳遞SESSION選擇在php.ini中關(guān)閉,這樣這個(gè)參數(shù)就不會(huì)出現(xiàn)在URL中了,需要的朋友可以參考下2014-07-07Ajax請(qǐng)求PHP后臺(tái)接口返回信息的實(shí)例代碼
今天小編就為大家分享一篇Ajax請(qǐng)求PHP后臺(tái)接口返回信息的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08PHP命名空間namespace及use的簡(jiǎn)單用法分析
這篇文章主要介紹了PHP命名空間namespace及use的簡(jiǎn)單用法,結(jié)合實(shí)例形式分析了php命名空間的功能、使用方法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2018-08-08基于asp+ajax和數(shù)據(jù)庫(kù)驅(qū)動(dòng)的二級(jí)聯(lián)動(dòng)菜單
基于asp+ajax和數(shù)據(jù)庫(kù)驅(qū)動(dòng)的二級(jí)聯(lián)動(dòng)菜單,需要的朋友可以參考下。2010-05-05PHP處理JSON字符串key缺少雙引號(hào)的解決方法
這篇文章主要介紹了PHP處理JSON字符串key缺少雙引號(hào)的解決方法,是非常常見(jiàn)的一類(lèi)錯(cuò)誤處理情況,需要的朋友可以參考下2014-09-09PHP結(jié)合Ffmpeg快速搭建流媒體服務(wù)的實(shí)踐記錄
這篇文章主要給大家介紹了關(guān)于使用PHP結(jié)合Ffmpeg快速搭建流媒體服務(wù)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10