會(huì )話(huà)模塊無(wú)法保證你存儲在會(huì )話(huà)中的信息只能被創(chuàng )建會(huì )話(huà)的用戶(hù)本人可見(jiàn)。 你需要采取額外的手段來(lái)保護會(huì )話(huà)中的機密信息, 至于采取何種方式來(lái)保護機密信息, 取決于你在會(huì )話(huà)中存儲的數據的機密程度。
評估會(huì )話(huà)中存儲的數據的重要性,
以及為此增加額外的保護機制,
通常需要付出一定的代價(jià),同時(shí)會(huì )降低便利性。
例如,如果你需要保護用戶(hù)免受社會(huì )工程學(xué)攻擊,
你需要啟用 session.use_only_cookies
選項。
這就要求用戶(hù)在使用過(guò)程中,必須把瀏覽器設置為接受 cookie,
否則就無(wú)法正常使用會(huì )話(huà)功能了。
有很多種方式都可以導致會(huì )話(huà) ID 被泄露給第三方。 例如,JavaScript 注入,URL 中包含會(huì )話(huà) ID,數據包偵聽(tīng), 或者直接訪(fǎng)問(wèn)你的物理設備等。 如果會(huì )話(huà) ID 被泄漏給第三方, 那么他們就可以訪(fǎng)問(wèn)這個(gè)會(huì )話(huà) ID 可以訪(fǎng)問(wèn)的全部資源。 首先,如果在 URL 中包含了會(huì )話(huà) ID, 并且訪(fǎng)問(wèn)了外部的站點(diǎn), 那么你的會(huì )話(huà) ID 可能在外部站點(diǎn)的訪(fǎng)問(wèn)日志中被記錄(referrer 請求頭)。 另外,攻擊者也可以監聽(tīng)你的網(wǎng)絡(luò )通信,如果通信未加密, 那么會(huì )話(huà) ID 將會(huì )在網(wǎng)絡(luò )中以明文的形式進(jìn)行傳輸。 針對這種情況的解決方案就是在服務(wù)端配置 SSL/TLS, 另外,使用 HSTS 可以達到更高的安全性。
注意: 即使使用 HTTPS 協(xié)議,也無(wú)法百分百保證機密數據不被泄漏。 例如,CRIME 和 BEAST 漏洞可以使得攻擊者讀取到你的數據。 另外,出于網(wǎng)絡(luò )通信審計目的,很多網(wǎng)絡(luò )中都存在 HTTPS MITM 代理, 可以讀取 HTTPS 協(xié)議下的通信數據。 那么攻擊者也可以搭建類(lèi)似的代理服務(wù)器,用來(lái)竊取 HTTPS 協(xié)議下的通信數據。
目前,默認情況下,PHP 是以自適應的方式來(lái)管理會(huì )話(huà)的, 這種方式使用起來(lái)很靈活,但是同樣也帶來(lái)了一定的風(fēng)險。
新增加了一個(gè)配置項: session.use_strict_mode。 當啟用這個(gè)配置項,并且你所用的會(huì )話(huà)存儲處理器支持的話(huà),未經(jīng)初始化的會(huì )話(huà) ID 會(huì )被拒絕, 并為其生成一個(gè)全新的會(huì )話(huà),這可以避免攻擊者使用一個(gè)已知的會(huì )話(huà) ID 來(lái)進(jìn)行攻擊。 例如,攻擊者可以通過(guò)郵件給受害者發(fā)送一個(gè)包含會(huì )話(huà) ID 的鏈接: http://example.com/page.php?PHPSESSID=123456789。 如果啟用了 session.use_trans_sid 配置項, 那么受害者將會(huì )使用攻擊者所提供的會(huì )話(huà) ID 開(kāi)始一個(gè)新的會(huì )話(huà)。 如果啟用了 session.use_strict_mode 選項,就可以降低風(fēng)險。
用戶(hù)自定義的會(huì )話(huà)存儲器也可以通過(guò)實(shí)現會(huì )話(huà) ID 驗證來(lái)支持嚴格會(huì )話(huà)模式。 建議用戶(hù)在實(shí)現自己的會(huì )話(huà)存儲器的時(shí)候, 一定要對會(huì )話(huà) ID 的合法性進(jìn)行驗證。
在瀏覽器一側,可以為用來(lái)保存會(huì )話(huà) ID 的 cookie 設置域,路徑, 僅允許 HTTP 訪(fǎng)問(wèn),必須使用 HTTPS 訪(fǎng)問(wèn)等安全屬性。 如果使用的是 PHP 7.3. 版本,還可以對 cookie 設置 SameSite 屬性。 攻擊者可以利用瀏覽器的這些特性來(lái)設置永久可用的會(huì )話(huà) ID。 僅僅設置 session.use_only_cookies 配置項 無(wú)法解決這個(gè)問(wèn)題。而 session.use_strict_mode 配置項 可以降低這種風(fēng)險。設置 session.use_strict_mode=On, 來(lái)拒絕未經(jīng)初始化的會(huì )話(huà) ID。
注意: 雖然使用 session.use_strict_mode 配置項 可以降低靈活會(huì )話(huà)管理方式所帶來(lái)的風(fēng)險, 攻擊者還是通過(guò)利用 JavaScript 注入等手段, 強制用戶(hù)使用由攻擊者創(chuàng )建的并且經(jīng)過(guò)了正常的初始化的會(huì )話(huà) ID。 如何降低這種風(fēng)向,可以參考本手冊的建議部分。 如果你已經(jīng)啟用了 session.use_strict_mode 配置項, 同時(shí)使用基于時(shí)間戳的會(huì )話(huà)管理, 并且通過(guò)設置 session_regenerate_id() 配置項 來(lái)重新生成會(huì )話(huà) ID, 那么,攻擊者生成的會(huì )話(huà) ID 就可以被刪除掉了。 當發(fā)生對過(guò)期會(huì )話(huà)訪(fǎng)問(wèn)的時(shí)候, 你應該保存活躍會(huì )話(huà)的所有數據, 以備后續分析使用。 然后讓用戶(hù)退出當前的會(huì )話(huà),并且重新登錄。 防止攻擊者繼續使用“偷”來(lái)的會(huì )話(huà)。
對過(guò)期會(huì )話(huà)數據的訪(fǎng)問(wèn)并不總是意味著(zhù)正在遭受攻擊。 不穩定的網(wǎng)絡(luò )狀況,或者不正確的會(huì )話(huà)刪除行為, 都會(huì )導致合法的用戶(hù)產(chǎn)生訪(fǎng)問(wèn)過(guò)期會(huì )話(huà)數據的情況。
從 PHP 7.1.0 開(kāi)始,增加了 session_create_id() 函數。 這個(gè)函數允許開(kāi)發(fā)者在會(huì )話(huà) ID 中增加用戶(hù) ID 作為前綴, 以確保用戶(hù)訪(fǎng)問(wèn)到正確對應的會(huì )話(huà)數據。 要使用這個(gè)函數, 請確保啟用了 session.use_strict_mode 配置項, 否則惡意用戶(hù)可能會(huì )偽造其他用戶(hù)的會(huì )話(huà) ID。
注意: 對于 PHP 7.1.0 之前的用戶(hù),應該使用 CSPRNG(例如 /dev/urandom) 或者 random_bytes() 函數以及哈希函數 來(lái)產(chǎn)生新的會(huì )話(huà) ID。 session_create_id() 函數本身包含碰撞檢測的能力, 并且根據 INI 文件中和會(huì )話(huà)相關(guān)的配置項來(lái)生成會(huì )話(huà) ID。 所以,建議使用 session_create_id() 函數來(lái)生成會(huì )話(huà) ID。
雖然 session.use_strict_mode 配置項可以降低風(fēng)險,但是還不夠。為了確保會(huì )話(huà)安全,開(kāi)發(fā)者還需要使用 session_regenerate_id() 函數。
會(huì )話(huà) ID 重生機制可以有效的降低會(huì )話(huà)被竊取的風(fēng)險, 所以,必須周期性的調用 session_regenerate_id() 函數 來(lái)重新生成會(huì )話(huà) ID, 例如,對于機密內容,每隔 15 分鐘就重新生成會(huì )話(huà) ID。 這樣一來(lái),即使會(huì )話(huà) ID 被竊取, 那么攻擊者所得到的會(huì )話(huà) ID 也會(huì )很快的過(guò)期, 如果他們進(jìn)一步訪(fǎng)問(wèn),就會(huì )產(chǎn)生對過(guò)期會(huì )話(huà)數據訪(fǎng)問(wèn)的錯誤。
當用戶(hù)成功通過(guò)認證之后,必須為其重新生成會(huì )話(huà) ID。 并且,必須在向 $_SESSION 中保存用戶(hù)認證信息之前 調用 session_regenerate_id() 函數( session_regenerate_id() 函數 會(huì )自動(dòng)將重生之前的會(huì )話(huà)數據保存到新生成的會(huì )話(huà))。 請確保只有新的會(huì )話(huà)包含用戶(hù)認證信息。
開(kāi)發(fā)者不可過(guò)分依賴(lài) session.gc_maxlifetime 配置項。 因為攻擊者可以在受害者的會(huì )話(huà)過(guò)期之前訪(fǎng)問(wèn)系統, 并且維持這個(gè)會(huì )話(huà)的活動(dòng),以保證這個(gè)會(huì )話(huà)不會(huì )過(guò)期。
實(shí)際上,你需要自己實(shí)現基于時(shí)間戳的會(huì )話(huà)數據管理機制。
雖然會(huì )話(huà)管理器可以透明的管理時(shí)間戳,但是這個(gè)特性尚未完整的實(shí)現。
在 GC 發(fā)生之前,舊的會(huì )話(huà)數據還得保存,
同時(shí),開(kāi)發(fā)者還得保證過(guò)期的會(huì )話(huà)數據已經(jīng)被移除。
但是,開(kāi)發(fā)者又不能立即移除活躍會(huì )話(huà)中的數據。
所以,不要同時(shí)在活躍會(huì )話(huà)上調用 session_regenerate_id(true);
和 session_destroy() 函數。
這聽(tīng)起來(lái)有點(diǎn)兒自相矛盾,但是事實(shí)上必須得這么做。
默認情況下,session_regenerate_id() 函數 不會(huì )刪除舊的會(huì )話(huà), 所以即使重生了會(huì )話(huà) ID,舊的會(huì )話(huà)可能還是可用的。 開(kāi)發(fā)者需要使用時(shí)間戳等機制, 來(lái)確保舊的會(huì )話(huà)數據不會(huì )再次被訪(fǎng)問(wèn)。
刪除活躍會(huì )話(huà)可能會(huì )帶來(lái)非預期的一些影響。 例如,在網(wǎng)絡(luò )狀態(tài)不穩定,或者有并發(fā)請求到達 Web 服務(wù)器的情況下, 立即刪除活躍會(huì )話(huà)可能導致個(gè)別請求會(huì )話(huà)失效的問(wèn)題。
立即刪除活躍會(huì )話(huà)也無(wú)法檢測可能存在的惡意訪(fǎng)問(wèn)。
作為替代方案, 你要在 $_SESSION 中設置一個(gè)很短的過(guò)期時(shí)間, 然后根據這個(gè)時(shí)間戳來(lái)判斷后續的訪(fǎng)問(wèn)是被允許的還是被禁止的。
在調用 session_regenerate_id() 函數之后, 不能立即禁止對舊的會(huì )話(huà)數據的訪(fǎng)問(wèn),應該再一小段之間之后再禁止訪(fǎng)問(wèn)。 例如,在穩定的網(wǎng)絡(luò )條件下,可以設置為幾秒鐘, 在不穩定的網(wǎng)絡(luò )條件下,可以設置為幾分鐘。
如果用戶(hù)訪(fǎng)問(wèn)了舊的會(huì )話(huà)數據(已經(jīng)過(guò)期的), 那么應該禁止訪(fǎng)問(wèn)。 建議從會(huì )話(huà)中移除這個(gè)用戶(hù)的認證信息,因為這看起來(lái)像是在遭受攻擊。
如果攻擊者設置了不可刪除的 cookie,那么使用 session.use_only_cookies 和 session_regenerate_id() 會(huì )導致正常用戶(hù)遭受拒絕服務(wù)的問(wèn)題。 如果發(fā)生這種情況,請讓用戶(hù)刪除 cookie 并且警告用戶(hù)他可能面臨一些安全問(wèn)題。 攻擊者可以通過(guò)惡意的 Web 應用、瀏覽器插件以及對安全性較差的物理設備進(jìn)行攻擊 來(lái)偽造惡意的 cookie。
請勿誤解這里的拒絕服務(wù)攻擊風(fēng)險所指的含義。
通常來(lái)講,要保護會(huì )話(huà) ID 的安全,use_strict_mode=On
是必須要做的。
建議所有的站點(diǎn)都啟用 use_strict_mode=On
。
只有當賬號處于被攻擊的時(shí)候,才會(huì )發(fā)生拒絕服務(wù)的問(wèn)題。 通常都是由于應用中被注入了惡意的 JavaScript 才會(huì )導致這個(gè)問(wèn)題。
過(guò)期的會(huì )話(huà)中的數據應該是被刪除的,并且不可訪(fǎng)問(wèn)。 現在的會(huì )話(huà)模塊尚未很好的支持這種特性。
應該盡可能快的刪除過(guò)期會(huì )話(huà)中的數據。 但是,活躍會(huì )話(huà)一定不要立即刪除。 為了能夠同時(shí)滿(mǎn)足這兩點(diǎn)要求, 你需要自己來(lái)實(shí)現基于時(shí)間戳的會(huì )話(huà)數據管理機制。
在 $_SESSION 中設置會(huì )話(huà)過(guò)期時(shí)間戳,并且對其進(jìn)行管理, 以便能夠阻止對于過(guò)期會(huì )話(huà)的訪(fǎng)問(wèn)。 當發(fā)生對于過(guò)期會(huì )話(huà)的訪(fǎng)問(wèn)時(shí),建議從相關(guān)用戶(hù)的所有會(huì )話(huà)中刪除認證信息, 并且要求用戶(hù)重新認證。 對于過(guò)期會(huì )話(huà)數據的訪(fǎng)問(wèn)可能是一種攻擊行為, 為了保護會(huì )話(huà)數據,你需要追蹤每個(gè)用戶(hù)的活躍會(huì )話(huà)。
注意: 當用戶(hù)處于不穩定的網(wǎng)絡(luò ),或者 web 應用存在并發(fā)的請求的時(shí)候, 也可能發(fā)生對于過(guò)期會(huì )話(huà)數據的訪(fǎng)問(wèn)。 服務(wù)器嘗試為用戶(hù)設置新的會(huì )話(huà) ID, 但是很可能由于網(wǎng)絡(luò )原因,導致 Set-Cookie 的數據包無(wú)法到達用戶(hù)的瀏覽器。 當通過(guò) session_regenerate_id() 函數 為一個(gè)連接生成新的會(huì )話(huà) ID 之后,其他的并發(fā)連接可能尚未得到這個(gè)新的會(huì )話(huà) ID。 因此,不能立即阻止對于過(guò)期會(huì )話(huà)數據的訪(fǎng)問(wèn),而是要延遲一個(gè)很小的時(shí)間段, 這就是為什么我們需要實(shí)現基于時(shí)間戳的會(huì )話(huà)管理。
簡(jiǎn)而言之,不要在調用 session_regenerate_id() 或者 session_destroy() 函數的時(shí)候立即刪除舊的會(huì )話(huà)數據, 而是要通過(guò)一個(gè)時(shí)間戳來(lái)控制后續對于這個(gè)舊會(huì )話(huà)數據的訪(fǎng)問(wèn)。 從會(huì )話(huà)存儲中刪除數據的工作交給 session_gc() 函數來(lái)完成吧。
默認情況下,為了保證會(huì )話(huà)數據在多個(gè)請求之間的一致性, 對于會(huì )話(huà)數據的訪(fǎng)問(wèn)是加鎖進(jìn)行的。
但是,這種鎖定機制也會(huì )導致被攻擊者利用,來(lái)進(jìn)行對于用戶(hù)的拒絕服務(wù)攻擊。
為了降低這種風(fēng)險,請在訪(fǎng)問(wèn)會(huì )話(huà)數據的時(shí)候,盡可能的縮短鎖定的時(shí)間。
當某個(gè)請求不需要更新會(huì )話(huà)數據的時(shí)候,使用只讀模式訪(fǎng)問(wèn)會(huì )話(huà)數據。
也就是說(shuō),在調用 session_start() 函數的時(shí)候,
使用 'read_and_close' 選項:session_start(['read_and_close'=>1]);
。
另外,如果需要更新會(huì )話(huà)數據,那么在更新完畢之后,
馬上調用 session_commit() 函數來(lái)釋放對于會(huì )話(huà)數據的鎖。
當會(huì )話(huà)不活躍的時(shí)候,當前的會(huì )話(huà)模塊不會(huì )檢測對于 $_SESSION 的修改。 你需要自己來(lái)保證 在會(huì )話(huà)處于不活躍狀態(tài)的時(shí)候,不要去修改它。
開(kāi)發(fā)者需要自己來(lái)追蹤每個(gè)用戶(hù)的活躍會(huì )話(huà), 要知道每個(gè)用戶(hù)創(chuàng )建了多少活躍會(huì )話(huà),每個(gè)活躍會(huì )話(huà)來(lái)自那個(gè) IP 地址, 活躍了多長(cháng)時(shí)間等。 PHP 不會(huì )自動(dòng)完成這項工作,需要開(kāi)發(fā)者來(lái)完成。
有很多種方式可以做到追蹤用戶(hù)的活躍會(huì )話(huà)。 你可以通過(guò)在數據庫中存儲會(huì )話(huà)信息來(lái)跟蹤用戶(hù)會(huì )話(huà)。 由于會(huì )話(huà)是可以被垃圾收集器收集掉的, 所以你也需要處理被收集掉的會(huì )話(huà)數據, 以保證數據庫中的數據和真實(shí)的活躍會(huì )話(huà)數據的一致性。
一種很簡(jiǎn)單的方式就是使用“使用用戶(hù) ID 作為會(huì )話(huà) ID 前綴”,并且保存必要的信息到 $_SESSION 中。 大部分的數據庫產(chǎn)品對于字符串前綴查詢(xún)(譯注:也即右模糊查詢(xún),可以利用索引)都有很好的性能表現。 為了實(shí)現這種方式,可以使用 session_regenerate_id() 和 session_create_id() 函數。
永遠不要使用機密數據作為會(huì )話(huà) ID 前綴! 如果用戶(hù) ID 屬于機密數據,那么可以考慮使用 hash_hmac() 函數對其進(jìn)行摘要后再使用。
必須啟用 session.use_strict_mode 配置項。 請確保已經(jīng)啟用, 否則活躍會(huì )話(huà)數據庫可能會(huì )被入侵。
要能夠檢測對于過(guò)期會(huì )話(huà)數據的訪(fǎng)問(wèn), 基于時(shí)間戳的會(huì )話(huà)數據管理機制是必不可少的。 當檢測到對于過(guò)期會(huì )話(huà)數據的訪(fǎng)問(wèn)時(shí),你應該從相關(guān)用戶(hù)的活躍會(huì )話(huà)中刪除認證信息, 避免攻擊者持續使用盜取的會(huì )話(huà)。
開(kāi)發(fā)者不應該通過(guò)使用長(cháng)生命周期的會(huì )話(huà) ID 來(lái)實(shí)現自動(dòng)登錄功能, 因為這種方式提高了會(huì )話(huà)被竊取的風(fēng)險。 開(kāi)發(fā)者應該自己實(shí)現自動(dòng)登錄的機制。
在使用 setcookie() 的時(shí)候,傳入安全的一次性摘要結果作為自動(dòng)登錄信息。 建議使用比 SHA-2 更高強度的摘要算法(例如 SHA-256) 對 random_bytes() 隨機生成的數據 (也可以讀取 /dev/urandom 設備)進(jìn)行摘要作為自動(dòng)登錄的信息。
在用戶(hù)訪(fǎng)問(wèn)的時(shí)候,如果發(fā)現用戶(hù)尚未認證, 那么就去檢查請求中是否包含了有效的一次性登錄信息。 如果包含有效的一次性登錄信息,那么就去認證用戶(hù),并且重新生成新的一次性登錄信息。 自動(dòng)登錄的關(guān)鍵信息一定是只能使用一次,永遠不要重復使用一次性登錄信息。
自動(dòng)登錄信息是長(cháng)生命周期的認證信息, 所以必須要盡可能的妥善保護。 可以對于自動(dòng)登錄信息對應的 cookie 設置路徑、僅允許 HTTP 訪(fǎng)問(wèn)、僅允許安全訪(fǎng)問(wèn) 等屬性來(lái)加以保護,并且僅在必需的時(shí)候才傳送這個(gè) cookie。
開(kāi)發(fā)者也要提供禁用自動(dòng)登錄的機制, 以及刪除不再需要的自動(dòng)登錄數據的能力。
會(huì )話(huà)和認證無(wú)法避免跨站請求偽造攻擊。 開(kāi)發(fā)者需要自己來(lái)實(shí)現保護應用不受 CSRF 攻擊的功能。
output_add_rewrite_var() 函數可以用來(lái) 保護應用免受 CSRF 攻擊。更多信息請參考文檔。
注意: PHP 7.2.0 之前的版本,這個(gè)函數和會(huì )話(huà) ID 使用了同樣的輸出緩沖以及 INI 設置項, 所以不建議在 PHP 7.2.0 之前使用 output_add_rewrite_var() 函數。
大部分 Web 應用框架都提供了 CSRF 保護的特性。 詳細信息請參考你所用的 Web 框架的文檔。
從 PHP 7.3 開(kāi)始,對于會(huì )話(huà) cookie 增加了 SameSite 屬性, 這個(gè)屬性可以有效的降低 CSRF 攻擊的風(fēng)險。