PHP安全程式設計最佳實踐

weixin_33670713發表於2017-08-17

前言

大家都知道由於PHP其簡單、快速、方便、易於開發且專注於Web領域等特性,使得PHP在目前的Web後端依舊稱霸。但是,因為其動態特性等一系列因素使得我們在程式設計時依舊犯下錯誤,最後導致bug的產生。
而我最近在看stackoverflow時,無意中發現關於PHP安全程式設計的相關知識。因此,將其記錄在部落格中便於日後查詢。

安全程式設計

PHP版本號洩露

  • 問題
628297-917ed036c919280c.png
image.png

如上圖所示,一般預設我們在向伺服器請求資料時,返回的響應頭中 “X-Powered-By”顯示了我們伺服器使用的PHP版本。這其實是不安全的,因為PHP存在時間已經有20多年了每個版本都或多或少都有些bug。如果NB的黑客知道你的PHP的版本號,極有可能利用這些bug攻擊我們的電腦。

  • 解決方案

① 在php.ini配置檔案中,設定expose_php = off
② 在PHP程式碼中直接使用 header("X-Powered-By: Magic"); 修改X-Powered-By內容
③ 在PHP程式碼中直接使用 header_remove("X-Powered-By");

XSS攻擊

  • 問題

XSS攻擊產生的原因在於伺服器沒有對客戶端上傳的資料進行校驗,導致客戶端上傳一段js程式碼上來。最後,導致在我們渲染頁面的時候,將該js程式碼也渲染到網頁上從而產生意外。

如下面程式碼所示:

628297-18fb0c9318769dee.png
image.png

前端程式碼index.html,後臺程式碼b.php。當我們從瀏覽器開啟index.html後點選提交,b.php直接從POST請求獲取了資料並直接輸出到頁面,結果就是頁面直接輸出js程式碼。想象一下黑客不是彈出hello world,而是無限迴圈或者直接將頁面元素全部刪除後果是咋樣。

628297-9d2c8db299e2ddc3.png
image.png
  • 解決方案
<?php

// 使用htmlspecialchars將字串中html標籤進行處理
echo '<div>' . htmlspecialchars($_GET['input']) . '</div>';
// 獲取使用下面的方式 當然使用htmlspecialchars更加方便
echo '<div>' . filter_input(INPUT_GET, 'input', FILTER_SANITIZE_SPECIAL_CHARS) . '</div>';

// 而且對於動態生成的URL,也可以使用如下方式來保證安全
<?php
// 使用urlencode來編碼URL
$input = urlencode($_GET['input']);
// 或者使用如下方式
$input = filter_input(INPUT_GET, 'input', FILTER_SANITIZE_URL);
echo '<a href="http://example.com/page?input="' . $input . '">Link</a>';

而且也開始使用第三方庫htmlpurifier來過濾HTML程式碼

CSRF攻擊

  • 問題

    CSRF攻擊原理在於使用了使用者瀏覽器上的cookie。我們在使用網銀時會先登入,然後本地瀏覽器會產生cookie用來標識使用者,之後我們的每一次向伺服器請求都會攜帶該cookie而伺服器也是根據該cookie判斷使用者是否有許可權進行操作。比如說:銀行有一個轉賬請求URL: https://www.example.com?account=xiaoming&money=2000&target=xiaofeng,正常情況下使用者發出該請求後 伺服器在校驗cookie後通過該請求完成轉賬。
    但是,黑客也可能是該網銀的使用者也知道有這條轉賬的請求,因此,寫了一個頁面 裡面有一個連結 <a href="https://www.example.com?account=xiaoming&money=20000&target=heike">,然後,黑客誘導使用者進入該網頁並點選了該連結,如果使用者剛剛已經登入網銀而cookie還在有效期,則在使用者點選該連結後瀏覽器會攜帶有效cookie並向伺服器進行轉賬操作,伺服器看到這個cookie認為是使用者本人在進行轉賬操作而將20000元轉給heike。因此,在使用者不知道的情況完成了一個轉賬給heike的合法操作。

  • 解決方案

<form method="get" action="/delete.php">
  <input type="text" name="accnt" placeholder="accnt number" />
  <input type="hidden" name="csrftoken" value="<randomToken>" />
  <input type="submit" />
</form>

如上所示,每一次瀏覽器向伺服器請求頁面時伺服器會動態生成csrftoken放入表單中,然後瀏覽器向伺服器傳送請求都攜帶csrftoken。這樣伺服器會驗證csrftoken檢查是否是有效請求。
當然,你也可以將其使用ajax請求並將csrftoken放入header。獲取你也可以同時檢查HTTP_REFERER以及csrftoken來驗證是否是合理請求

命令列注入

  • 問題

該注入產生的原因還是在於伺服器沒有對客戶端傳入的資料進行校驗而直接利用資料完成操作,最後導致嚴重的後果。
例如:

<pre>
<?php system('ls ' . $_GET['path']); ?>
</pre>

如果傳入的$_GET['path']字串是 "; rm -rf /",那麼就會執行 ls;rm -rf /命令。最後伺服器就會將根目錄的資料全部刪除。

  • 解決方案
<pre>
// 使用escapeshellarg或者escapeshellcmd命令對資料進行過濾
<?php system('ls ' . escapeshellarg($_GET['path'])); ?>
</pre>

上面執行的程式碼為 ls '; rm -fr /'

SQL隱碼攻擊

  • 問題

SQL隱碼攻擊產生原因還是在於對客戶端傳入的資料沒有進行過濾並直接用來拼接SQL語句,這也是以前PHP產生初期黑客攻擊的常用方式。

  • 解決方案

1) 對客戶端的資料進行檢查,過濾一些字元
2) 使用PDO進行預編譯來繫結引數,從而查詢資料庫。目前,如果使用PHP框架也很多針對SQL的解決方案,例如:Yii2框架中AR查詢·

error_reporting

  • 問題

在我們編寫PHP程式碼中經常會在瀏覽器出現 狀態碼500 (不要跟我說你從來沒有遇到過),如果PHP配置中開啟了展示錯誤提示的功能,那麼一些伺服器的錯誤資訊會顯示在瀏覽器上。這樣會讓人知道伺服器的相關資訊,那麼怎樣讓這些錯誤資訊不要顯示在瀏覽器上而輸出到伺服器特定位置,而這就需要用到set_error_handler函式。

  • 解決方案

① 若沒有開啟PHP錯誤提示的功能,可寫如下程式碼臨時開啟

ini_set("display_errors", "On");
error_reporting(E_ALL | E_STRICT);

② 根據自己的業務要求,將下面的程式碼修改後放入自己的PHP程式碼中,下面程式碼的意思是PHP傳送錯誤時會呼叫該函式,然後我通過實現該函式就可以將錯誤資訊重定向到特定位置。

set_error_handler(function($errno , $errstr, $errfile, $errline){
  try{
    $pdo = new PDO("mysql:host=hostname;dbname=databasename", 'dbuser', 'dbpwd', [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ]);

    if($stmt = $pdo->prepare("INSERT INTO `errors` (no,msg,file,line) VALUES (?,?,?,?)")){
      if(!$stmt->execute([$errno, $errstr, $errfile, $errline])){
        throw new Exception('Unable to execute query');
      }
    } else {
      throw new Exception('Unable to prepare query');
    }
  } catch (Exception $e){
    error_log('Exception: ' . $e->getMessage() . PHP_EOL . "$errfile:$errline:$errno | $errstr");
  }
});

其他的還有遠端檔案包含以及上傳檔案攻擊,大家可以檢視https://stackoverflow.com/documentation/php/2781/security#t=201708170948498596789瞭解,它們的攻擊原因都在於對瀏覽器傳入的資料麼有進行過濾。

總結

黑客攻擊手段多種多樣,但黑客之所以能夠攻擊到伺服器在於伺服器對於客戶端傳入的資料沒有進行校驗。因此,要保證伺服器的安全性必須要防禦性程式設計思想,特別是那些涉及敏感資料要多多思考。

相關文章