SQL 注入
攻擊者通過構造惡意SQL指令發送到資料庫,如果程式未對使用者輸入的 SQL指令執行判斷過濾,那麼生成的SQL語句可能會繞過安全性檢查,插入其他用于修改後端資料庫的語句,并可能執行系統指令,進而對系統造成危害
例如删除 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 時輸入 1 OR 1 ,你的語句會組合成這樣:
$sql = "DELETE FROM posts WHERE user_id = 1 AND id = 1 OR 1";
一般比較常出現在原生的 SQL 操作,架構一般會解決這方面的問題。通常采用參數控制或過濾特殊字元避免上述的問題。
越權漏洞
1. 水準越權
水準越權就是同等角色下的使用者,不但能夠通路和操作自己私有的資料,還能通路其他人私有的資料,其根本是基于資料的通路權限。
删除使用者收款方式的場景如下:
graph LR
使用者登入-->token
token-->擷取收款方式清單
擷取收款方式清單--token-->通過id删除
通過收款方式 {id} 執行 delete 請求的路由為: localhost/api/payments/{id}
假如使用者A的收款方式有{1,2,3} 使用者B的收款方式有{4,5}
如果沒有做資料控制,A 登入後攜帶 A 的 token 執行删除的接口 localhost/api/payments/4,則會删除 B 的,是以需要對 destory 方法做資料控制
# 1 删除前鑒權處理
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 模型關聯查詢
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();
}
}
推薦使用第三種方式做資料控制,不然面向對象白學了。擷取收款方式的清單同樣需要資料權限控制,使用者和收款方式存在一對多的關聯關系,模型關聯後,擷取使用者收款方式清單可以寫成
class PaymentController extends Controller
{
public function index($id)
{
#帶條件的查詢
$payments = $this->currentUser->payments()->where(function($query){
...
})->get();
#不帶條件的查詢
$payments = $this->currentUser->payments;
}
}
2. 垂直越權
低權限的角色通過一些途徑,獲得高權限的能力,就發生了越權通路。如普通使用者 guest 修改 admin 使用者的密碼;guest 可直接進入背景取得域名管理、使用者管理等所有權限
解決這個問題,需要把權限職責以最小顆粒細分,基于 RBAC 設計權限管理系統。分為以下關聯模型
graph LR
使用者--多對多-->角色
使用者--多對多-->權限
角色--多對多-->權限
每次執行請求時,在前置中間件判斷這個使用者是否永遠該執行請求的權限,無權限則駁回。
3. 上下文越權
攻擊者能夠利用應用程式狀态機中的漏洞獲得關鍵資源的通路權限,這就存在上下文相關的越權。上下文相關的越權漏洞一般屬于業務邏輯漏洞。 如在找回密碼過程中,攻擊者使用自己的賬戶資訊通過驗證,将他人的密碼進行了修改。
graph LR
1.郵箱驗證-->2.找回密碼
在步驟1之後,執行找回密碼,路由為 。如果此時沒有校驗目前找回密碼的賬戶是否為進行郵箱校驗後的賬戶,由可能産生越權漏洞.
路由 : 【PUT 】localhost/api/users/find-password,接收參數 email,new_password.
錯誤:校驗和修改分成 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 裡面再次驗證完成郵箱校驗的賬戶是否為目前找回密碼的賬号
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();
}
...
}
}
限制分頁條目範圍,防止惡意請求
如獲文章清單的接口 localhost/api/articles
public function index($params)
{
$pageId = $params['pageid'] ?? PAGE_ID; //頁碼
$pageSize = $params['pagesize'] ?? 15; //條碼
$articles = Article::where(function ($query) use ($params) {
...
})->take($pageSize)->skip($pageId * $pageSize)->orderby('id', 'desc')->get();
...
...
}
以上代碼如果沒有限制 pagesize 的範圍,惡意請求者請求把 pagesize 輸入 5000,10000 等甚至更大的數,會給資料庫帶來一定的壓力,localhost/api/articles?pageid=0&pagesize=10000
//用架構自帶的分頁方法
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可以解密擷取 加密主鍵、載荷等重要資訊,是以通常需要對JWT的token進行二次加密
限制上傳檔案的類型
對于一個圖檔上傳的接口,如果沒有對上傳檔案的格式做限制,攻擊者很有可能把 .php字尾的檔案上傳到public/images目錄下,然後通過根目錄執行這個檔案。
需要設計安全的檔案上傳功能避免上述問題
- 檔案上傳的目錄設定為不可執行
- 判斷檔案類型
- 使用随機數改寫檔案名和檔案路徑
- 單獨設定檔案伺服器的域名
禁止或者避免寫自動解壓.zip 等壓縮檔案的代碼
單純地限制檔案或壓縮包大小并沒有用,一個ZIP炸彈的.zip檔案僅有 42 KB,但在解壓後會占用 4718592 GB
避免登入密碼被暴力破解
- 設定嚴格的速率限制,如登入次數限制,登入錯誤次數達 x 次時暫停登入 n 分鐘
- 密碼加上随機鹽
public function reg()
{
$user = new User;
$salt = radom(6);
$user->password = bcrypt($data['password'] . $salt);
...
}
做好異常處理,避免在生産環境中不正确的錯誤報告暴露敏感資料
如果你不小心,可能會在生産環境中因為不正确的錯誤報告洩露了敏感資訊,例如:檔案夾結構、資料庫結構、連接配接資訊與使用者資訊。
- 在.env 檔案中關閉調試模式
APP_DEBUG=true
- php 錯誤控制 error_reporting、display_errors
<?php // 關閉錯誤報告 error_reporting(0); // 報告 runtime 錯誤 error_reporting(E_ERROR | E_WARNING | E_PARSE); // 報告所有錯誤 error_reporting(E_ALL); // 等同 error_reporting(E_ALL); ini_set("error_reporting", E_ALL); // 報告 E_NOTICE 之外的所有錯誤 error_reporting(E_ALL & ~E_NOTICE); ?>
display_errors = Off
php 弱語言的設計缺陷如:in_array
$array=[0,1,2,'3'];
var_dump(in_array('abc', $array)); //true
var_dump(in_array('1bc', $array)); //true
# 上面的情況傳回的都是 true, 因為’abc’會轉換為 0,’1bc’轉換為 1
$a = null;
$b = false;
echo $a==$b; //true
$c = "";
$d = 0;
echo $c==$d //true
在一些重要的地方需要使用 === 來作資料判斷。
LFI (本地檔案包含)
LFI (本地檔案包含) 是一個使用者未經驗證從磁盤讀取檔案的漏洞。
不驗證過濾使用者的輸入 将它要渲染的模闆檔案用 GET 請求加載。
<body>
<?php
$page = $_GET['page'];
if(!$page) {
$page = 'main.php';
}
include($page);
?>
</body>
由于 Include 可以加載任何檔案,不僅僅是 PHP,攻擊者可以将系統上的任何檔案作為包含目标傳遞。
index.php?page=../../etc/passwd
這将導緻 /etc/passwd 檔案被讀取并展示在浏覽器上。
要防禦此類攻擊,你必須仔細考慮允許使用者輸入的類型,并删除可能有害的字元,如輸入字元中的 “.” “/” “\”。
XSS
XSS 又叫 CSS (Cross Site Script) ,跨站腳本攻擊。它指的是惡意攻擊者往 Web 頁面裡插入惡意 html 代碼,當使用者浏覽該頁之時,嵌入其中 Web 裡面的 html 代碼會被執行,進而達到惡意攻擊使用者的特殊目的。
<body>
<?php
$searchQuery = $_GET['q'];
/* some search magic here */
?>
<h1>You searched for: <?php echo $searchQuery; ?></h1>
</body>
因為我們把使用者的内容直接列印出來,不經過任何過濾,非法使用者可以拼接 URL: search.php?q=%3Cscript%3Ealert(1)%3B%3C%2Fscript%3E
PHP 渲染出來的内容如下,可以看到 Javascript 代碼會被直接執行:
<body>
<h1>You searched for: <script>alert(1);</script></h1>
<p>We found: Absolutely nothing because this is a demo</p>
</body>
Javascript 可以:
- 偷走你使用者浏覽器裡的 Cookie;
- 通過浏覽器的記住密碼功能擷取到你的站點登入賬号和密碼;
- 盜取使用者的機密資訊;
- 你的使用者在站點上能做到的事情,有了 JS 權限執行權限就都能做,也就是說 A 使用者可以模拟成為任何使用者;
- 在你的網頁中嵌入惡意代碼;
使用 htmlentities() 過濾特殊字元,防止大部分的 xss 攻擊
CSRF (跨站請求僞造)
例如網站上有使用者可以用來登出賬戶的連結。
<a href="http://your-website.com/delete-account">銷毀賬戶</a>
如果某個使用者評論:
<img src=”http://your-website.com/delete-account”> wow
使用者将在檢視此評論的時候删除他們的賬号。