自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

PHP安全問題入門:10個常見安全問題+實例講解

開發(fā) 架構
相對于其他幾種語言來說, PHP 在 web 建站方面有更大的優(yōu)勢,即使是新手,也能很容易搭建一個網(wǎng)站出來。但這種優(yōu)勢也容易帶來一些負面影響,因為很多的 PHP 教程沒有涉及到安全方面的知識。

[[261471]]

相對于其他幾種語言來說, PHP 在 web 建站方面有更大的優(yōu)勢,即使是新手,也能很容易搭建一個網(wǎng)站出來。但這種優(yōu)勢也容易帶來一些負面影響,因為很多的 PHP 教程沒有涉及到安全方面的知識。

此帖子分為幾部分,每部分會涵蓋不同的安全威脅和應對策略。但是,這并不是說你做到這幾點以后,就一定能避免你的網(wǎng)站出現(xiàn)任何問題。如果你想提高你的網(wǎng)站安全性的話,你應該繼續(xù)通過閱讀書籍或者文章,來研究如何提高你的網(wǎng)站安全性

出于演示需要,代碼可能不是很完美。日常開發(fā)過程中,很多代碼都包含在了框架跟各種庫里面。作為一個后臺開發(fā),你不僅要熟練基本的CURD,更要知道如何保護你的數(shù)據(jù)。

1. SQL 注入

我賭一包辣條,你肯定會看到這里。 SQL 注入是對您網(wǎng)站最大的威脅之一,如果您的數(shù)據(jù)庫受到別人的 SQL 注入的攻擊的話,別人可以轉出你的數(shù)據(jù)庫,也許還會產(chǎn)生更嚴重的后果。

網(wǎng)站要從數(shù)據(jù)庫中獲取動態(tài)數(shù)據(jù),就必須執(zhí)行 SQL 語句,舉例如下: 

  1. <?php  
  2. $username = $_GET['username'];  
  3. $query = "SELECT * FROM users WHERE username = '$username'"

攻擊者控制通過 GET 和 POST 發(fā)送的查詢(或者例如 UA 的一些其他查詢)。一般情況下,你希望查詢戶名為「 peter 」的用戶產(chǎn)生的 SQL 語句如下: 

  1. SELECT * FROM users WHERE username = 'peter' 

但是,攻擊者發(fā)送了特定的用戶名參數(shù),例如:' OR '1'='1

這就會導致 SQL 語句變成這樣: 

  1. SELECT * FROM users WHERE username = 'peter' OR '1' = '1' 

這樣,他就能在不需要密碼的情況下導出你的整個用戶表的數(shù)據(jù)了。

那么,我們?nèi)绾畏乐惯@類事故的發(fā)生呢?主流的解決方法有兩種。轉義用戶輸入的數(shù)據(jù)或者使用封裝好的語句。轉義的方法是封裝好一個函數(shù),用來對用戶提交的數(shù)據(jù)進行過濾,去掉有害的標簽。但是,我不太推薦使用這個方法,因為比較容易忘記在每個地方都做此處理。

下面,我來介紹如何使用 PDO 執(zhí)行封裝好的語句( mysqi 也一樣): 

  1. $username = $_GET['username'];  
  2. $query = $pdo->prepare('SELECT * FROM users WHERE username = :username');  
  3. $query->execute(['username' => $username]);  
  4. $data = $query->fetch(); 

動態(tài)數(shù)據(jù)的每個部分都以:做前綴。然后將所有參數(shù)作為數(shù)組傳遞給執(zhí)行函數(shù),看起來就像 PDO 為你轉義了有害數(shù)據(jù)一樣。

幾乎所有的數(shù)據(jù)庫驅動程序都支持封裝好的語句,沒有理由不使用它們!養(yǎng)成使用他們的習慣,以后就不會忘記了。

你也可以參考 phpdelusions 中的一篇關于動態(tài)構建 SQL 查詢時處理安全問題的文章。鏈接:  https://phpdelusions.net/pdo/... 。

2. XSS

XSS 又叫 CSS (Cross Site Script) ,跨站腳本攻擊。它指的是惡意攻擊者往 Web 頁面里插入惡意 html 代碼,當用戶瀏覽該頁之時,嵌入其中 Web 里面的 html 代碼會被執(zhí)行,從而達到惡意攻擊用戶的特殊目的。

下面以一個搜索頁面為例子: 

  1. <body>  
  2. <?php  
  3. $searchQuery = $_GET['q'];  
  4. /* some search magic here */  
  5. ?>  
  6. <h1>You searched for: <?php echo $searchQuery; ?></h1>  
  7. <p>We found: Absolutely nothing because this is a demo</p>  
  8. </body> 

因為我們把用戶的內(nèi)容直接打印出來,不經(jīng)過任何過濾,非法用戶可以拼接 URL: 

  1. search.php?q=%3Cscript%3Ealert(1)%3B%3C%2Fscript%3E 

PHP 渲染出來的內(nèi)容如下,可以看到 Javascript 代碼會被直接執(zhí)行: 

  1. <body>  
  2. <h1>You searched for: <script>alert(1);</script></h1>  
  3. <p>We found: Absolutely nothing because this is a demo</p>  
  4. </body> 

問:JS 代碼被執(zhí)行有什么大不了的?

Javascript 可以:

  •  偷走你用戶瀏覽器里的 Cookie;
  •  通過瀏覽器的記住密碼功能獲取到你的站點登錄賬號和密碼;
  •  盜取用戶的機密信息;
  •  你的用戶在站點上能做到的事情,有了 JS 權限執(zhí)行權限就都能做,也就是說 A 用戶可以模擬成為任何用戶;
  •  在你的網(wǎng)頁中嵌入惡意代碼;
  •  ...

問:如何防范此問題呢?

好消息是比較先進的瀏覽器現(xiàn)在已經(jīng)具備了一些基礎的 XSS 防范功能,不過請不要依賴與此。

正確的做法是堅決不要相信用戶的任何輸入,并過濾掉輸入中的所有特殊字符。這樣就能消滅絕大部分的 XSS 攻擊: 

  1. <?php  
  2. $searchQuery = htmlentities($searchQuery, ENT_QUOTES); 

或者你可以使用模板引擎 Twig ,一般的模板引擎都會默認為輸出加上 htmlentities 防范。

如果你保持了用戶的輸入內(nèi)容,在輸出時也要特別注意,在以下的例子中,我們允許用戶填寫自己的博客鏈接: 

  1. <body>  
  2.   <a href="<?php echo $homepageUrl; ?>">Visit Users homepage</a>  
  3. </body> 

以上代碼可能第一眼看不出來有問題,但是假設用戶填入以下內(nèi)容: 

  1. #" onclick="alert(1) 

會被渲染為: 

  1. <body>  
  2.   <a href="#" onclick="alert(1)">Visit Users homepage</a>  
  3. </body> 

永遠永遠不要相信用戶輸入的數(shù)據(jù),或者,永遠都假設用戶的內(nèi)容是有攻擊性的,態(tài)度端正了,然后小心地處理好每一次的用戶輸入和輸出。

另一個控制 XSS 攻擊的方法是提供一個 CSP Meta 標簽,或者標頭信息,更多詳情請見: https://www.html5rocks.com/en...

另外種 Cookie 時,如果無需 JS 讀取的話,請必須設置為 "HTTP ONLY"。這個設置可以令 JavaScript 無法讀取 PHP 端種的 Cookie。

3. XSRF/CSRF

CSRF 是跨站請求偽造的縮寫,它是攻擊者通過一些技術手段欺騙用戶去訪問曾經(jīng)認證過的網(wǎng)站并運行一些操作。

雖然此處展示的例子是 GET 請求,但只是相較于 POST 更容易理解,并非防護手段,兩者都不是私密的 Cookies 或者多步表單。

假如你有一個允許用戶刪除賬戶的頁面,如下所示: 

  1. <?php  
  2. //delete-account.php  
  3. $confirm = $_GET['confirm'];  
  4. if($confirm === 'yes') {  
  5.   //goodbye  

攻擊者可以在他的站點上構建一個觸發(fā)這個 URL 的表單(同樣適用于 POST 的表單),或者將 URL 加載為圖片誘惑用戶點擊: 

  1. <img src="https://example.com/delete-account.php?confirm=yes" /> 

用戶一旦觸發(fā),就會執(zhí)行刪除賬戶的指令,眨眼你的賬戶就消失了。

防御這樣的攻擊比防御 XSS 與 SQL 注入更復雜一些。

最常用的防御方法是生成一個 CSRF 令牌加密安全字符串,一般稱其為 Token,并將 Token 存儲于 Cookie 或者 Session 中。

每次你在網(wǎng)頁構造表單時,將 Token 令牌放在表單中的隱藏字段,表單請求服務器以后會根據(jù)用戶的 Cookie 或者 Session 里的 Token 令牌比對,校驗成功才給予通過。

由于攻擊者無法知道 Token 令牌的內(nèi)容(每個表單的 Token 令牌都是隨機的),因此無法冒充用戶。 

  1. <?php /* 你嵌入表單的頁面 */ ?>  
  2. <form action="/delete-account.php" method="post">  
  3.   <input type="hidden" name="csrf" value="<?php echo $_SESSION['csrf']; ?>">  
  4.   <input type="hidden" name="confirm" value="yes" />  
  5.   <input type="submit" value="Delete my account" />  
  6. </form>  
  7. ##   
  8. <?php  
  9. //delete-account.php  
  10. $confirm = $_POST['confirm'];  
  11. $csrf = $_POST['csrf'];  
  12. $knownGoodToken = $_SESSION['csrf'];  
  13. if($csrf !== $knownGoodToken) {  
  14.   die('Invalid request');  
  15.  
  16. if($confirm === 'yes') {  
  17.   //goodbye  

請注意,這是個非常簡單的示例,你可以加入更多的代碼。如果你使用的是像 Symfony 這樣的 PHP 框架,那么自帶了 CSRF 令牌的功能。

你還可以查看關于 OWASP 更詳細的問題和更多防御機制的文章: https://github.com/OWASP/CheatS....

4. LFI

LFI (本地文件包含) 是一個用戶未經(jīng)驗證從磁盤讀取文件的漏洞。

我經(jīng)常遇到編程不規(guī)范的路由代碼示例,它們不驗證過濾用戶的輸入。我們用以下文件為例,將它要渲染的模板文件用 GET 請求加載。 

  1. <body>  
  2. <?php  
  3.   $page = $_GET['page'];  
  4.   if(!$page) {  
  5.     $page = 'main.php' 
  6.   }  
  7.   include($page);  
  8. ?>  
  9. </body> 

由于 Include 可以加載任何文件,不僅僅是PHP,攻擊者可以將系統(tǒng)上的任何文件作為包含目標傳遞。 

  1. index.php?page=../../etc/passwd 

這將導致 /etc/passwd 文件被讀取并展示在瀏覽器上。

要防御此類攻擊,你必須仔細考慮允許用戶輸入的類型,并刪除可能有害的字符,如輸入字符中的“.” “/” “”。

如果你真的想使用像這樣的路由系統(tǒng)(我不建議以任何方式),你可以自動附加 PHP 擴展,刪除任何非 [a-zA-Z0-9-_] 的字符,并指定從專用的模板文件夾中加載,以免被包含任何非模板文件。

我在不同的開發(fā)文檔中,多次看到造成此類漏洞的 PHP 代碼。從一開始就要有清晰的設計思路,允許所需要包含的文件類型,并刪除掉多余的內(nèi)容。你還可以構造要讀取文件的絕對路徑,并驗證文件是否存在來作為保護,而不是任何位置都給予讀取。

5. 不充分的密碼哈希

大部分的 Web 應用需要保存用戶的認證信息。如果密碼哈希做的足夠好,在你的網(wǎng)站被攻破時,即可保護用戶的密碼不被非法讀取。

首先,最不應該做的事情,就是把用戶密碼明文儲存起來。大部分的用戶會在多個網(wǎng)站上使用同一個密碼,這是不可改變的事實。當你的網(wǎng)站被攻破,意味著用戶的其他網(wǎng)站的賬號也被攻破了。

其次,你不應該使用簡單的哈希算法,事實上所有沒有專門為密碼哈希優(yōu)化的算法都不應使用。哈希算法如 MD5 或者 SHA 設計初衷就是執(zhí)行起來非??臁_@不是你需要的,密碼哈希的終極目標就是讓黑客花費無窮盡的時間和精力都無法破解出來密碼。

另外一個比較重要的點是你應該為密碼哈希加鹽(Salt),加鹽處理避免了兩個同樣的密碼會產(chǎn)生同樣哈希的問題。

以下使用 MD5 來做例子,所以請千萬不要使用 MD5 來哈希你的密碼, MD5 是不安全的。

假如我們的用戶 user1 和 user315 都有相同的密碼 ilovecats123,這個密碼雖然看起來是強密碼,有字母有數(shù)字,但是在數(shù)據(jù)庫里,兩個用戶的密碼哈希數(shù)據(jù)將會是相同的:5e2b4d823db9d044ecd5e084b6d33ea5 。

如果一個如果黑客拿下了你的網(wǎng)站,獲取到了這些哈希數(shù)據(jù),他將不需要去暴力破解用戶 user315 的密碼。我們要盡量讓他花大精力來破解你的密碼,所以我們對數(shù)據(jù)進行加鹽處理: 

  1. <?php  
  2. //warning: !!這是一個很不安全的密碼哈希例子,請不要使用!!  
  3. $password = 'cat123' 
  4. $salt = random_bytes(20);  
  5. $hash = md5($password . $salt); 

最后在保存你的唯一密碼哈希數(shù)據(jù)時,請不要忘記連 $salt 也已經(jīng)保存,否則你將無法驗證用戶。

在當下,最好的密碼哈希選項是 bcrypt,這是專門為哈希密碼而設計的哈希算法,同時這套哈希算法里還允許你配置一些參數(shù)來加大破解的難度。

新版的 PHP 中也自帶了安全的密碼哈希函數(shù) password_hash ,此函數(shù)已經(jīng)包含了加鹽處理。對應的密碼驗證函數(shù)為 password_verify 用來檢測密碼是否正確。password_verify 還可有效防止 時序攻擊.

以下是使用的例子: 

  1. <?php  
  2. //user signup  
  3. $password = $_POST['password'];  
  4. $hashedPassword = password_hash($password, PASSWORD_DEFAULT);  
  5. //login  
  6. $password = $_POST['password'];  
  7. $hash = '1234'; //load this value from your db  
  8. if(password_verify($password, $hash)) {  
  9.   echo 'Password is valid!';  
  10. } else {  
  11.   echo 'Invalid password.';  

需要澄清的一點是:密碼哈希并不是密碼加密。哈希(Hash)是將目標文本轉換成具有相同長度的、不可逆的雜湊字符串(或叫做消息摘要),而加密(Encrypt)是將目標文本轉換成具有不同長度的、可逆的密文。顯然他們之間最大的區(qū)別是可逆性,在儲存密碼時,我們要的就是哈希這種不可逆的屬性。

6. 中間人攻擊

MITM (中間人) 攻擊不是針對服務器直接攻擊,而是針對用戶進行,攻擊者作為中間人欺騙服務器他是用戶,欺騙用戶他是服務器,從而來攔截用戶與網(wǎng)站的流量,并從中注入惡意內(nèi)容或者讀取私密信息,通常發(fā)生在公共 WiFi 網(wǎng)絡中,也有可能發(fā)生在其他流量通過的地方,例如ISP運營商。

對此的唯一防御是使用 HTTPS,使用 HTTPS 可以將你的連接加密,并且無法讀取或者篡改流量。你可以從 Let's Encrypt 獲取免費的 SSL 證書,或從其他供應商處購買,這里不詳細介紹如何正確配置 WEB 服務器,因為這與應用程序安全性無關,且在很大程度上取決于你的設置。

你還可以采取一些措施使 HTTPS 更安全,在 WEB 服務器配置加上 Strict-Transport-Security 標示頭,此頭部信息告訴瀏覽器,你的網(wǎng)站始終通過 HTTPS 訪問,如果未通過 HTTPS 將返回錯誤報告提示瀏覽器不應顯示該頁面。

然而,這里有個明顯的問題,如果瀏覽器之前從未訪問過你的網(wǎng)站,則無法知道你使用此標示頭,這時候就需要用到 Hstspreload。

可以在此注冊你的網(wǎng)站: https://hstspreload.org/

你在此處提交的所有網(wǎng)站都將被標記為僅 HTTPS,并硬編碼到 Google Chrome、FireFox、Opera、Safari、IE11 和 Edge 的源代碼中。

你還可以在 DNS 配置中添加 Certification Authority Authorization (CAA) record ,可以僅允許一個證書頒發(fā)機構(例如: Let's encrypt)發(fā)布你的域名證書,這進一步提高了用戶的安全性。

7. 命令注入

這可能是服務器遇到的最嚴重的攻擊,命令注入的目標是欺騙服務器執(zhí)行任意 Shell 命令

你如果使用 shell_exec 或是 exec 函數(shù)。讓我們做一個小例子,允許用戶簡單的從服務器 Ping 不同的主機。 

  1. <?php  
  2. $targetIp = $_GET['ip'];  
  3. $output = shell_exec("ping -c 5 $targetIp"); 

輸出將包括對目標主機 Ping 5次。除非采用 sh 命令執(zhí)行 Shell 腳本,否則攻擊者可以執(zhí)行想要的任何操作。 

  1. ping.php?ip=8.8.8.8;ls -l /etc 

Shell 將執(zhí)行 Ping 和由攻擊者拼接的第二個命令,這顯然是非常危險的。

感謝 PHP 提供了一個函數(shù)來轉義 Shell 參數(shù)。

escapeshellarg 轉義用戶的輸入并將其封裝成單引號。 

  1. <?php  
  2. $targetIp = escapeshellarg($_GET['ip']);  
  3. $output = shell_exec("ping -c 5 $targetIp"); 

現(xiàn)在你的命令應該是相當安全的,就個人而言,我仍然避免使用 PHP 調(diào)用外部命令,但這完全取決于你自己的喜好。

另外,我建議進一步驗證用戶輸入是否符合你期望的形式。

8. XXE

XXE (XML 外部實體) 是一種應用程序使用配置不正確的 XML 解析器解析外部 XML 時,導致的本地文件包含攻擊,甚至可以遠程代碼執(zhí)行。

XML 有一個鮮為人知的特性,它允許文檔作者將遠程和本地文件作為實體包含在其 XML 文件中。 

  1. <?xml version="1.0" encoding="ISO-8859-1"?>  
  2.  <!DOCTYPE foo [  
  3.    <!ELEMENT foo ANY >  
  4.    <!ENTITY passwd SYSTEM "file:///etc/passwd" >]>  
  5.    <foo>&passwd;</foo> 

就像這樣, /etc/passwd 文件內(nèi)容被轉儲到 XML 文件中。

如果你使用 libxml 可以調(diào)用 libxml_disable_entity_loader 來保護自己免受此類攻擊。使用前請仔細檢查 XML 庫的默認配置,以確保配置成功。

9. 在生產(chǎn)環(huán)境中不正確的錯誤報告暴露敏感數(shù)據(jù)

[](https://secure.php.net/manual...,可能會在生產(chǎn)環(huán)境中因為不正確的錯誤報告泄露了敏感信息,例如:文件夾結構、數(shù)據(jù)庫結構、連接信息與用戶信息。

你是不希望用戶看到這個的吧?

一般根據(jù)你使用的框架或者 CMS ,配置方法會有不同的變化。通??蚣芫哂性试S你將站點更改為某種生產(chǎn)環(huán)境的設置。這樣會將所有用戶可見的錯誤消息重定向到日志文件中,并向用戶顯示非描述性的 500 錯誤,同時允許你根據(jù)錯誤代碼檢查。

但是你應該根據(jù)你的 PHP 環(huán)境設置: error_reporting 與 display_errors.

10. 登錄限制

像登錄這樣的敏感表單應該有一個嚴格的速率限制,以防止暴力攻擊。保存每個用戶在過去幾分鐘內(nèi)失敗的登錄嘗試次數(shù),如果該速率超過你定義的閾值,則拒絕進一步登錄嘗試,直到冷卻期結束。還可通過電子郵件通知用戶登錄失敗,以便他們知道自己的賬戶被成為目標。

一些其他補充

  •  不要信任從用戶傳遞給你的對象 ID ,始終驗證用戶對請求對象的訪問權限
  •  服務器與使用的庫時刻保持最新
  •  訂閱關注安全相關的博客,了解最新的解決方案
  •  從不在日志中保存用戶的密碼
  •  不要將整個代碼庫存儲在 WEB 根目錄中
  •  永遠不要在 WEB 根目錄創(chuàng)建 Git 存儲庫,除非你希望泄露整個代碼庫
  •  始終假設用戶的輸入是不安全的
  •  設置系統(tǒng)禁止可疑行為的 IP 顯示,例如:工具對 URL 隨機掃描、爬蟲
  •  不要過分信任第三方代碼是安全的
  •  不要用 Composer 直接從 Github 獲取代碼
  •  如果不希望站點被第三方跨域 iframe,請設置反 iframe 標示頭
  •  含糊是不安全的
  •  如果你是缺乏實踐經(jīng)驗的運營商或合作開發(fā)人員,請確保盡可能時常檢查代碼
  •  當你不了解安全功能應該如何工作,或者為什么會安裝,請詢問知道的人,不要忽視它
  •  永遠不要自己寫加密方式,這可能是個壞的方法
  •  如果你沒有足夠的熵,請正確播種你的偽隨機數(shù)生成并舍棄
  •  如果在互聯(lián)網(wǎng)上不安全,并有可能被竊取信息,請為這種情況做好準備并制定事件響應計劃
  •  禁用 WEB 根目錄列表顯示,很多 WEB 服務器配置默認都會列出目錄內(nèi)容,這可能導致數(shù)據(jù)泄露
  •  客戶端驗證是不夠的,需要再次驗證 PHP 中的所有內(nèi)容
  •  不惜一切代價避免反序列化用戶內(nèi)容,這可能導致遠程代碼執(zhí)行,有關此問題的詳細信息,請參閱此文章: https://paragonie.com/blog/20...

小貼士

我不是一個安全專家,恐無法做到事無巨細。盡管編寫安全軟件是一個非常痛苦的過程,但還是可以通過遵循一些基本規(guī)則,編寫合理安全的應用程序。其實,很多框架在這方面也幫我們做了很多工作。

在問題發(fā)生之前,安全性問題并不像語法錯誤等可以在開發(fā)階段追蹤到。因此,在編寫代碼的過程中,應該時刻有規(guī)避安全風險的意識。如果你迫于業(yè)務需求的壓力而不得不暫時忽略一些安全防范的工作,我想你有必要事先告知大家這樣做的潛在風險。

如果你從這篇文章有所收益,也請把它分享給你的朋友們把,讓我們共建安全網(wǎng)站。

 

責任編輯:龐桂玉 來源: segmentfault
相關推薦

2020-10-30 08:50:25

2016-06-28 11:28:11

2017-03-14 13:39:08

2021-03-13 20:45:11

安全Web策略

2023-06-14 11:59:55

2012-11-20 10:47:16

2014-10-21 10:30:33

2009-05-30 09:36:18

2018-08-02 15:40:59

2021-04-22 21:58:51

云計算IaaS安全

2022-02-24 09:00:00

AD安全漏洞

2015-07-06 09:52:40

2011-03-21 10:23:06

2013-04-02 11:07:16

2009-11-03 13:46:56

Oracle密碼

2011-11-17 10:34:14

內(nèi)網(wǎng)安全

2011-05-20 11:59:32

2013-09-05 09:42:06

2012-12-11 11:28:20

2015-04-21 10:23:11

點贊
收藏

51CTO技術棧公眾號