作者MoMoShota (正太控)
看板PHP
標題Re: [請益] 網站資安需注意哪些?
時間Mon Jun 4 22:28:38 2018
※ 引述《GALINE (天真可愛CQD)》之銘言:
我是原 PO,沒想到前幾天吃義大利麵前的文居然有人這麼詳細地回應,甚表感謝。
以下針對一些回應做一下回覆,期待能夠拋磚引玉激起更多的高手討論 <(_ _)>
: 想試著來全部回答一下,我本業不是網路安全,所以可能內容會需要被訂正...
: : 1. 像是 XSS 是什麼?會帶來什麼危險?用 htmlentities 跟 htmlspecialchars 這兩個函式來防禦有什麼差別?
: 白話解釋 XSS:「別人在你的網站上塞他的 javascript」
: 可能的危害
: - 所有使用者都會看到嘲笑你的對話方塊
: - 你的網站被塞別人的廣告
: - 別人可以用 javacsript 控制你的使用者做...任何在他在網站上能做的事情
: - 其他很多...
: htmlenties 會把「每一個字」都編碼成 html entity,htmlspecialchars 只轉換 & " ' > < 這五個。
: 在大多數情況下這兩者的防禦效果差不多,差異在於出來的檔案大小會不一樣。
: 不過我記得有個 UTF-7 什麼碗糕的的鬼可以打穿 htmlspecialchars,只要 charset 用 utf-8 就沒事
: 剛剛一下 Google 找到都是講 wordpress 爛掉的事情就是了...
這邊 G 大應該說反了,用 UTF-7 可以打穿的應該是 htmlentities。
目前主流的 template engine 多採用 htmlspecialchars 作為防禦手段。
如 Laravel 的 Blade:
https://bit.ly/2HlafbM
或 Symfony 的 Twig:
https://bit.ly/2HhILDO
XSS 的技術內容我推薦 TDOH 創辦人 Hrj 的簡報:
https://bit.ly/2JfCJWn
: : 2. LFI 是什麼?除了被 access 到 /etc/passwd 之外,有沒有可能讓 source code 洩露?
: 白話解釋 LFI:「別人可以亂讀你的檔案」
: a.php?file=aaa.doc => 吐 aaa.doc 的內容給他
: a.php?file=aaa.doc => 把 a.php 的內容(咦,不就自己嗎)吐給他
: 我看過比較有趣的搞法是讀 .git/ 把整個 repo 拿走,這樣還不用暴力硬掃你有哪些檔案...
LFI 常見於 PHP,但不限於 PHP。
最常見 LFI 出現於 PHP 的四大引入函式 include(_once) 或 require(_once)
它們可能會引入非預期的內容。
如果 allow_url_fopen 的設定有開的話(預設關閉),更可能造成 RCE。
而且也可以引入 php:// 串流,詳細可以參見:
https://bit.ly/2FN1TNq
: : 3. SQL Injection 的原理是?用 PDO 真的可以一勞永逸嗎? mysqli_real_escape_string 真的有助於防止 SQL injection 嗎?
: 組 SQL 字串的時候塞奇怪東西讓 SQL 的意思跟你以為的不一樣,然後幹奇怪的事情
: 例如
: $id = $_POST['id']; // '123or1=1'
: $sql = "select * from user where uid where id = {$id}";
: 出來會是 select * from user where uid where id = 123or1=1
: 然後所有的使用者資料就一起噴出來了
: 而且這個例子用 mysqli_real_escape_string 正好搞不定
: 理想的做法是不要自己組 SQL 字串,用 prepared statement 然後 bind 參數。背後做的事情大約像這樣
: mysql> PREPARE stmt FROM "SELECT * FROM user WHERE uid = ?";
: 「DB 哥哥,我等下要跑『select * from user where uid where id = ?』,問號我等下再跟你說是什麼」
: Statement prepared
: 「好喔,我準備好了等你的參數」
: mysql> SET @param='123or1=1'; EXECUTE stmt USING @param;
: 「DB 哥哥,參數是 '123or1=1' 喔,麻煩把結果跑給我」
: Query OK, 0 rows affected (0.00 sec)
: Empty set (0.00 sec)
: 「喔, id 是『123or1=1』嗎?我看看...一個都沒有喔!」
: 是說,我看到上面推文才知道 PDO 的 mysql driver 預設是用模擬的,不是真的叫 DB 做 prepared statement。
: 可以用 PDO::ATTR_EMULATE_PREPARES 這個選項切換...
: 爬了一下別人的 code
: Laravel 內部做 PDO 的時候會強制設定用真的 prepared statement
: doctrine 的 dbal 跟 maghead 乍看之下沒有管這件事
: 希望 PHP(或該說 MySQL Driver )模擬的效果夠好...
mysqli_real_escape_string 其實也不是毫無破綻的。
在 Discuz 某一版中,它會對過長的字串做截斷,在截斷之前該字串已經先做了一次過濾
造成 mysqli_real_escape_string 過濾後剛好被截斷,進而觸發 SQL Injection
: : 4. 什麼是 SSRF?會讓系統服務遭受什麼樣的風險?
: 白話解釋 SSRF:「別人讓你的 server 幫他送 request 給其他機器」
: 例如抓檔案的 URL 如果可以自己亂填
: upload_image.php?url=https://example.com/porn.png
: 然後你正好知道人家內部的網路狀況,或是剛好靈光一閃,就可以拿來打看看(?
: upload_image.php?url=https://dashboard.example.com/
: upload_image.php?url=http://127.0.0.1/
: 等於是讓人家間接鑽進內網幹亂連機器幹事情,而且你會以為是你的內部機器要連所以不一定擋得下來。
: SSRF 我不確定有沒有什麼好招可以處理,只能想到黑名單白名單....
一般來說,在原生 PHP 中處理 URL 下載的話,會使用 curl。
但是 curl 其實不僅支援 HTTP,其實還可以利用 gopher protocal 拿到 reverse shell
如果只要限制用 HTTP/HTTPS 的話,建議用 guzzlehttp 這個套件來處理。
然後用黑名單限制內網 domain。
: : 5. extract 這個函式很好用,但會不會不知不覺間被拿來提權?
: extract 把 array 的內容直接拆開變成變數
: 然後有些人會覺得 extract($_GET) 很好用
: 於是別人就可以亂打網址 ?var1=aaa&var2=bbb 來亂改你 code 裡面的變數內容,什麼魔法都能變得出來..
: 我覺得啦,這年頭還是別這樣寫...
extract 其實還有個風險是:它可以輕易覆蓋已宣告變數(預設設定)
它可以覆蓋包括但不限於 $_SEVER、$_SESSION 之類的變數。
: : 6. serialize 跟 unserialize 的使用時要注意什麼?
: 唔...我原本猜你指的是「unserialize 的東西可能是別人家過料的,然後就可以亂改變數內容」
: 類似 extract 的狀況
: 不過為防萬一找了一下,發現 PHP 跟 unserialize() 有關的安全漏洞還真不少....
: 我「猜」對 serialize 字串做數位簽章(例如 HMAC)驗證內容無誤,大概還擋得住。
: 不過更好的作法應該會是...不要 unserialize 任何使用者有機會動手腳(不管是網址帶入還是 post 進來)的東西。
serialize 跟 unserialize 其實是不限於 PHP 會發生的問題。
像是前陣子 Java Struts 2 的洞很多都是反序列化的問題(當晚很多人不睡覺呢,黑客打站,工程師補洞)
unserialize 的問題主要是因為一個 serialized class 在被 unserialize 時,會自動呼叫 __wake 這個魔術方法,就有機會達成 RCE。
所以在 unserialize 時盡量要去驗證來源是否完整(HMAC),或是嫌麻煩的話用 json_encode 及 json_decode
: : 7. Hash 用戶的密碼是用什麼演算法好呢?bcrypt
: : ?argon2?它們又有什麼差別?
: 簡單的答案:
: 什麼演算法都不要管,PHP(5.5+)都包好了
: 把密碼這樣存
: $hash = password_hash("password", PASSWORD_DEFAULT);
: 然後這樣比對密碼是否正確(別在 DB 比對)
: $passwordOK = password_verify($input, $storedHash);
: 然後用這裡的範例檢查有沒有需要更新密碼 hash
: http://php.net/manual/en/function.password-needs-rehash.php
: 最後,PHP 升級你盡量跟著升級。
: 真正的答案:
: 現代 hash 演算法的敵人主要有幾種
: ## 從數學,相對容易算出碰撞值
: 例如數學方法可以快速算出其他值的 hash 跟你密碼的 hash 一樣
: 對應法:
: 選沒有已知攻擊的演算法
: - md5 陣亡(2004的論文)
: - sha1 進棺材了(同一個人寫過 sha1 的論文,Google 做過示範攻擊)
: 還有,沒事就看一大堆講這種事情的文章....
: ## 從實作上被攻擊
: 有些實作有 side channel attack 的問題,例如可以從計算時間推算數值
: 甚至有些比較誇張的,可以錄你電腦的聲音推斷 CPU 多忙,猜出在算的值是什麼...
: 對應法:
: 常常更新系統套件,用 libsodium 或是 PHP 5.5+ 的 password_ 系列 function 來算 hash
: 還有,沒事就看一大堆講這種事情的文章....
: ## 預先算好巨大的對應表(所謂 rainbow table)
: 就是有人會先算好一大堆字串的 hash,做成查詢表方便對照。
: 所以看到 7c4a8d09ca3762af61e59520943dc26494f8941b 可以直接查出這是 123456 的 sha1
: 是說,有個網站叫做 CrackStation,線上讓你查 hash 對應表..
: 對應法:
: 內容加鹽或是給 iv,每筆資料的鹽或 iv 都要都不一樣,而且要夠長
: 這會讓對應表很難做
: 也可以用 PHP 5.5+ 的 password_ 系列算,他預設會隨機產鹽撒進資料來算 hash
: ## 用巨大的運算力量暴力硬算
: 通常是雲端出租 GPU,也有靠 ASIC 之類的專門機器。
: 感謝數位挖礦產業,這方面的技術突飛猛進...
: 對應法:
: 選難以平行化運算的,記憶體使用量大的,計算效能差的
: 然後也許因此不該選 sha256,因為 bitcoin 挖礦就是在算 sha256,有利可圖就有專門產業...
: bcrypt 是前幾年的佼佼者,而 argon2 是最近幾年的國際 hash 比賽冠軍。
: PHP 預設的密碼 hash PASSWORD_DEFAULT 應該還是 bcrypt
: 有找到 PHP 的 RFC 提到「暫時不把 PASSWORD_DEFAULT 改成 ARGON2」
: https://wiki.php.net/rfc/argon2_password_hash[B
: 不過理由我一下沒找到
: 然後「最好的 hash 演算法」是會隨著時間變化的
這邊其實已經講得滿詳細了,這邊多補充一下。
argon2 有針對大量 GPU 破解做防禦,如果是個新的專案,不妨試試用 argon2 作為 Hash 演算法
: : 8. json_encode 跟 json_decode 有什麼潛在問題嗎?有沒有可能造成 DoS?
: 其實我直覺想到的是「爬 log 的時候用 json_decode 好慢,我都用 jq 先 parse 過」...
: 不過這邊我猜你指的是可以惡搞 json 的內容,故意製造一大堆 hash collision,導致 hash table 效率低落。
: 這解釋起來得把 hash table 從頭講一次...簡中連結,不過講得還滿清楚的
: http://www.cnblogs.com/yangecnu/p/Introduce-Hashtable.html
json_encode 及 json_decode 目前使用的是 djb33,它效率很高,但沒這麼安全(容易受到 Hash-DoS 攻擊)。
目前 PHP Core 社群還沒有打算更改為 SipHash,效率也很高,但沒有 djb33 這麼高,不過更加安全。
現在為止最好的辦法是:
1. 在允許的範圍內,用 json 取代 serialize/unserialize
2. 最好可以用一個只有伺服器知道的 key 對 JSON 做 signature
3. 在無法驗證來源 JSON 時,應該限制 API Request 的速度
: : 9. 什麼是 XXE?什麼情況可能會出現?
: 我我我我我很少用 XML 這個麻煩的小東西所以跟他不熟........
XXE 常出現在跟既有系統介接時可能會發生的情況。
例如 Java 仍有許多系統是以 XML 作為資料交換的媒介。
XXE 其實源自於 XML Spec 下的美麗(?)誤會,它可能會造成 RCE。
: : 10. 什麼是 race codition?在 PHP + MySQL 這樣的經典組合中有沒有可能出現?要如何解決?
: 雷姆揍我右臉,我會往左邊飛
: 拉姆揍我左臉,我會往右邊飛
: 他們揍我的速度夠快,我被揍飛另一個揮拳就會落空
: 當雷姆跟拉姆幾乎同時出拳揍我,我可能往左邊飛,也可能往右邊飛,難以預測
: race condition 就是指這種狀況
: PHP 本身原則上是單一 Process 在跑的,所以只有 PHP 不太容易碰到
: (我知道有 fork 或 pthreads,但是網頁上還是盡量別這麼幹,跑後端 job 就是另外一件事...)
: MySQL 比較常碰到是當 DB 規模大的時候,通常會讀寫分離到不同台機器上。
: 如果先做 update 然後又馬上 select,有可能 select 不到更新後的資料(還沒同步到所有機器
: 另外一個狀況是來自系統外部,例如 client 同時發兩個取 token 的 request
: 然後你不確定 client 會覺得自己取到哪個
: race condition 有一些正面解法,例如說設置 lock 確保一次只有一個人通過
: 不過我的經驗是,非硬解不可的狀況並不多,比較建議的作法是調整工作流程
: 例如既然都 update 了表示你有資料,那就別再 select 了
: 或是跪求 client 不要同時發一樣的 request 上來,如果有 re-try 機制要間隔夠久
: 不讓拉姆跟雷姆有幾乎同時揍我的機會,就不會不知道自己被揍時會飛往何方
race condition 也算是個經典議題。
在設計高併發系統或最近很流行的 micro service 時很可能會碰上。
基本上 G 大說的 lock 是個解法,如果有興趣研究的話可以參考一下計算機組織。
: : 11. 如何正確取得使用者的 IP?$_SERVER['REMOTE_ADDR']?如果商業邏輯伺服器是放在 LoadBalancer 之後呢?
: $_SERVER 裡面有很多欄位跟 client IP 有關
: REMOTE_ADDR
: 這是 server 看到當下 request 的來源 IP
: HTTP_X_FORWARDED_FOR
: HTTP_FORWARDED_FOR
: HTTP_CLIENT_IP
: 這堆(或是其他類似的欄位)則是 http header 裡面寫的 client IP
: 如果你的伺服器是放在外面直接讓 client 打,那麼 HTTP_ 系列的欄位全部不可信
: 因為 http header 很容易偽造,人家可以隨便亂塞 127.0.0.1 之類的鬼東西
: 但如果機器放在 Load Balancer 後面,那麼送 request 給你的機器是 LB,REMOTE_ADDR 只會拿到 LB 的 IP
: 通常 LB 會確保 HTTP_X_FORWARDED_FOR 是原始的 client ip ,外部惡搞這個欄位的時候會也把他洗掉
: 這部分細節請參照 LB 的說明書
: 所以如何正確取得使用者 IP,要看你的的服務架構
: - 機器直接對外,看 $_SERVER['REMOTE_ADDR']
: - 機器在 LB 後面,看 $_SERVER['HTTP_X_FORWARDED_FOR'],或是看你的 LB 要你看哪個(些)欄位
這邊其實講得滿詳細的,我再補充一項。
因為 HTTP_X_FORWARDED_FOR, HTTP_FORWARDED_FOR 及 HTTP_CLIENT_IP 這三個是可以被竄改的。
所以如果在 Load Balancer 之後,通常來說會設定 trust proxy(symfony),踢除可信賴的 proxy 之後的第一個 IP 就是使用者 IP。
大概是這樣囉 _(:3UL)_,因為今天滿累的,所以文章寫的有點草率。
之後應該會整理成 Medium Post(有空的話),到時再來分享。
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 59.115.34.119
※ 文章網址: https://webptt.com/m.aspx?n=bbs/PHP/M.1528122524.A.24B.html
1F:推 GALINE: 唔,我記反啦 XDDDD 06/05 01:50
2F:→ MoMoShota: 沒事沒事,以前我也記反了 06/05 06:58
3F:推 shvanta: 學到新東西 感謝 06/05 09:35
4F:推 TFnight: 推 06/06 14:28
5F:推 DirtyVegas: 推推 06/07 19:29
6F:推 MOONRAKER: 讚 06/08 11:46
7F:推 kasimEnix: 讚 07/02 15:06