PHP 程式碼安全

summer-1994發表於2019-05-14

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.水平越權

水平越權就是同等角色下的使用者,不但能夠訪問和操作自己私有的資料,還能訪問其他人私有的資料,其根本是基於資料的訪問許可權。

刪除使用者收款方式的場景如下:

使用者登入-->獲取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 參入user_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目錄下,然後通過根目錄執行這個檔案。

需要設計安全的檔案上傳功能避免上述問題

  1. 檔案上傳的目錄設定為不可執行
  2. 判斷檔案型別
  3. 使用隨機數改寫檔名和檔案路徑
  4. 單獨設定檔案伺服器的域名

禁止或者避免寫自動解壓.zip等壓縮檔案的程式碼

單純地限制檔案或壓縮包大小並沒有用,一個ZIP炸彈的.zip檔案僅有 42 KB,但在解壓後會佔用 4718592 GB  

ZIP炸彈示例檔案

避免登入密碼被暴力破解

1. 設定嚴格的速率限制,如登入次數限制,登入錯誤次數達 x 次時暫停登入 n 分鐘
2. 密碼加上隨機鹽
    public function reg()
    {
        $user = new User;
        $salt = radom(6);
        $user->password = bcrypt($data['password'] . $salt);
        ...
    }

做好異常處理,避免在生產環境中不正確的錯誤報告暴露敏感資料

如果你不小心,可能會在生產環境中因為不正確的錯誤報告洩露了敏感資訊,例如:資料夾結構、資料庫結構、連線資訊與使用者資訊。
  1. 在.env檔案中關閉除錯模式
    APP_DEBUG=true
  2. 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

使用者將在檢視此評論的時候刪除他們的賬號。

laravel的web路由預設開啟了csrf驗證,原理是在客戶端產生一個隨機的token,在表單校驗時判斷這個token是否是這個頁面上的請求

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章