轉載 如何進行網站壓力測試

The Will Will Web | 如何進行網站壓力測試:以不動產交易實價查詢服務網為例

最近這兩天因為【內政部::不動產交易實價查詢服務網】網站上線而引起網友熱烈討論,雖���批評聲浪非常大,但我並不是要來數落這個網站的缺點,而是希望透過這個案例告訴大家網站壓力測試的重要性,尤其網站是那種可預期的大流量到來,網站建置廠商或客戶更應該提前做好準備,以確保網站能在預估的標準下正常運作。我相信,沒有人會反對網站上線前要做壓力測試,但我多年經驗下來發現,雖然手邊壓測工具都有,但普遍的問題是不知道怎樣測試?不知道測試的重點在哪?不知道壓測標準怎樣界定?不知道壓測報告怎樣閱讀與分析?今天想透過這篇文章來分享我在實務上進行網站壓力測試的經驗。

網站壓力測試相關的技術領域

因為網站壓力測試牽涉的範圍非常廣泛,並不是非常容易進行,也因為牽扯到的知識領域很廣,所以真正能確實做好壓力測試的人並不多。其中可能牽涉到的技術領域有:

設備管理

壓力測試必須使用專屬的電腦設備,盡量不要使用 VM (虛擬機器) 實施壓力測試,這樣才能真正模擬出較為真實的壓力測試數據。

要實施專業的網站壓力測試,應該會有以下設備要事先準備:

受測系統主機:對於要被施加壓力的主機

測試控制器主機:由控制器主機發動測試並收集壓力測試過程中所有數據

測試代理程式主機:由多台測試代理程式主機實際發動測試,對受測系統施加壓力

網路交換器(Switch):在負載平衡的網站架構下可能需要網管型 Switch 的設備支援

負載平衡器(Load Balancer):測試過程可能會用到硬體式的負載平衡器設備

防火牆設備(Firewall):測試過程可能會需要測試出防火牆設備的吞吐量(Throughput)

系統管理 / 網路管理

在一個完整的測試環境下,需要事先準備的工作非常多,可能包括的規劃項目有:

受測環境網路規劃

進行壓力測試時,建議排除「網路」可能造成的任何影響,也就是不要將受測主機擺在遠端,最好是將壓測主機與受測主機都擺在同一個區域網路(LAN)裡來測試,因為我們主要測試的是受測主機實際吞吐量,加入了網路變數後壓力數據就會嚴重失真,那麼這份報告的精確度也不會高。

是否要設置一個獨立的 IP 網段做壓力測試?

建議盡量用獨立網段作業,不要跟公司內網正在用的網段來測試,不然可能會影響公司其他同仁的網路運作。

透過負載平衡器做壓測可能會受到 IP 來源的影響而導致負載不夠均衡,因此建議可用多個 IP 同時進行壓測,這功能在 Visual Studio 2010 Ultimate 裡找的到。

AD 環境建置

由於必須模擬出生產環境(Production Environment)的部署架構,所以很有可能需要建置一個壓力測試用的 AD 環境。

IIS 網站伺服器調校

網站部署後必須調校 IIS 設定以調出最佳性能,所以在壓力測試的過程中可能要不斷微調 IIS 伺服器的設定。

壓力測試軟體安裝與設定

壓力測試種類繁多,且設計邏輯不同,所以要選對產品進行壓測才對。

網站負載平衡環境架設

測試過程可能會用到硬體式的負載平衡器設備或是微軟 Windows 伺服器內建的 NLB (Network Load Balance) 服務,這些都需要進行設定與調整。

網路交換器(Switch)管理與設定

建議使用網管型網路交換器,因為像是 NLB 這類的網路負載平衡服務可能需要依賴 Switch 設備提供的 IGMP Snooping 功能來設定 Multicast 相關參數。參考資料:精通 NLB:單點傳播(Unicast) 與 多點傳送(Multicast) 的差異

除此之外,當流量超過 GbE 等級的話,可能要換成 10GbE 的網路交換器,或是使用 Trunk 方式合併多個 Ports 的流量,以增加主機可負荷的流量。

防火牆設定與負載分析

我在實務上偶爾會看到有客戶做了壓力測試,卻在上線後掛在防火牆設備,因為防火牆等級無法應付大量的用戶端連線 (Sessions) 而導致防火牆過於忙碌,反而影響的網站的效能表現。因此當防火牆加入壓力測試的範疇後,應該要進一步分析在壓力測試的進行中防火牆的性能表現,如 CPU, Memory, Connections, Sessions 等各種數據表現。

網路流量監控與報表

測試過程中最好使用 GbE 等級的網路設備進行測試,如果預期流量會超過 GbE 等級的話,也必須進一步監控流經這些設備的流量,你可安裝CactiMRTG進行流量監控,也可自行收取設備的 SNMP 封包進行分析。

壓測工具

能夠實施網站壓力測試的工具非常多,從組合屋到豪宅等級的都有,當然你選用「豪宅」等級的壓測工具價格可能也不太便宜。在此我列出一些常聽到的壓力測試工具如下:

前端程式開發與調校

後端程式開發與調校

說實在的,後端程式的效能調校也是五花八門,只能考實戰累積出經驗才能真正上手,不過最基本就是做好完善的快取策略。

資料庫管理與調校

壓力測試的過程中,通常會與資料庫系統息息相關,如果網站沒有妥善規劃快取策略,只要在高壓之下很快就能看出你的資料庫系統負載過高,這時就必須要對資料庫做出適當的調校,例如遺漏索引分析、資料庫死結監控、資料庫封鎖監控、交易紀錄檔變化、…等等。

也因為進行壓力測試可以測出非常多原本不會發現的問題,因此所有相關的技術知識也必須具備才能真正調好網站效能。

網站壓力測試,到底測的是什麼?

在進行網站壓力測試時,最重要的是先搞清楚「目標」是什麼?你可能會想,不就是測「壓力」嗎?給他超大壓力,然後讓網站掛掉,就知道能承受多少壓力了嗎?

事實上,網站壓力測試不僅要知道系統的耐壓程度,另外還有一個很重要的測試標的是「網站穩定性」。

我們公司前幾天就正在對一個客戶的網站做壓力測試,因為這次網站從去年就已經上線,同樣的程式在去年已經進行過壓力測試,也非常順利的通過同時數千人連線的考驗,今年客戶委託我們做出一些功能調整並改版上線,在上線之前我們又再次進行壓力測試,這次不一樣的是,雖然壓力測試都通過驗證,數據也很漂亮,不過當我們以同時上線人數 1,500 人的情況下連續測試 10 分鐘,就在第 6 分鐘時開始發生程式錯誤,有時找不到網頁、有時引發 HTTP 500 Server Error 錯誤,非常的詭異,經程式開發人員的調整下發現原來是 web.config 沒有設定正確的 <machineKey> 導致壓測過程的狀態在一段時間後引發資料同步問題,這並不是單純測試壓力就能看的出來的,「時間」也是一個非常重要的穩定性因子。

除此之外,網站壓力測試還需要訂定出性能指標,當然客戶可以用他們自己的角度來定義,但網站的人數多寡並不是用「喊」的,你還是必須先透過實際的數據進行分析,分析出網站在最尖峰的時刻有多少人上線,網站伺服器有多少同時的連線與要求,最長的回應時間有多少,這些都必須從各個設備收集數據並分析瞭解後才能做出判斷,否則盲目的定義壓測目標,只會讓事情難以進行而已。

另一種常見的情況是,客戶可能會直接拿其他網站的性能指標來用,由於每個網站的需求不同、架構不同、用戶的屬性不同、可能連同時上線人數的需求都不太一樣,直接用其他網站的性能目標會讓網站開發團隊十分受傷,因為很有可能這網站永遠達不到性能目標。

所以,網站壓力測試測的是:

網站耐壓程度

在一定時間內不斷加壓,同時收錄/分析壓測過程中所有設備的效能數據,當任何一個設備發生效能瓶頸時,就是這個網站的效能極限(效能臨界值),並不是網站或資料庫主機掛了才叫「最大極限」。

網站穩定度

在效能臨界值的壓力下,施以一定時間的壓力測試,例如:10 分鐘、1 小時、2 小時等。

在一定時間內如果網站主機還能夠穩定的提供服務,才能代表這個網站經的起長時間的效能考驗。

在網站穩定度不佳的情況下,有時候我們會看到網站程式有記憶體洩漏(Memory Leaks)的問題,在長時間的壓力測試下由於記憶體不會被釋放,所以也會導致網站掛掉,這時就可以針對這些不容易發現的問題進行處理。

網站性能指標

有時候我們會定義網站壓力測試的性能指標是每個頁面的回應時間在特定大量下必須在幾秒內回應,這些是性能指標,必須透過量化數據來改善問題,千萬不要用感覺來分析問題。

這項指標最常見的謬誤就是,網頁呈現的速度慢,但伺服器與資料庫的負載都很輕,怎樣調整效能都無法改善,所以就算壓力測試報表數據很漂亮,但客戶還是不能接受的情況。事實上,壓力測試工具都無法幫你測試出網站實際在瀏覽器呈現網頁的效能,這部份屬於「前端程式開發與調校」,必須由網站前端工程師進一步做調校才能改善。

最後,我們就以不動產交易實價查詢服務網為例,搭配 Visual Studio 2012 Ultimate 的Web 效能和負載測試專案工具來進行網站壓力測試,各位可以看看如何落實這網站的壓力測試:

※由於我們不是該網站的建置廠商,只能先以「模擬數據」進行分析,文末也不會真的對該網站做壓測,純粹做示範性的案例分析。

環境分析

測試設備

設備 1 :受測系統主機 Web1 (採用 NLB 架構)

設備 2 :受測系統主機 Web2 (採用 NLB 架構)

設備 3 :受測系統資料庫主機 DB1

設備 4 :測試控制器主機 VSController1

設備 5 :測試代理程式主機 VSAgent1

設備 6 :測試代理程式主機 VSAgent2

設備 7 :網管型網路交換器 1 台

設備 8 :硬體式防火牆 1 台 ( 可承受 200,000 Sessions )

網路環境

受測系統:

Web1: 172.16.44.1

Web2: 172.16.44.2

DB 1: 172.16.44.9

壓測設備:

VSController1: 172.16.44.10

VSAgent1: 172.16.44.101 ~ 172.16.44.150

VSAgent2: 172.16.44.151 ~ 172.16.44.200

系統設定

確保所有測試設備間的網路連線是否設定正確,還有 Visual Studio 2012 Ultimate 所需的RemoteRegistry服務是否開啟,如果沒開啟將無法收錄各電腦之間的效能計數器資料。

網站部署

性能指標

由於這是一個全新的網站,無法收集真實的性能指標,但因為不動產實價登錄這議題還算夯,所以無論如何在壓力測試之前還是可以先壓一個概略性的性能指標,以下是預定的壓測目標:

可同時間承受 500 人連線

在高壓之下所有靜態頁面的頁面回應時間需在 3 秒以內完成

針對幾個重要的資料庫查詢表單,執行查詢時需在 10 秒內完成查詢並顯示結果

針對表單的思考時間(即使用者輸入資料的時間)約 15 秒鐘

錄製測試情境腳本

依據網站操作動線 (需規劃) 進行測試情境錄製,一個網站應該不只一個操作情境,而是依據不同的網站入口點與不同的操作模式進行規劃,然後設計出多種不同的測試情境。假設我錄製首頁進入後進到「不動產買賣」單元並進行一次搜尋的操作情境,錄製完成後如下圖示:

使用 Visual Studio 2012 Ultimate 錄製時也會紀錄每一次操作的考慮時間 (Think Time),你可以在錄製完成後從屬性窗格看到「考慮時間」的屬性設定。

設計負載測試範本

測試情境錄製完成後要設計出如何對受測系統施加壓力,所以我們要新增一個「負載測試」項目:

壓測時應加入使用者的「考慮時間」

設定負載模式時,建議使用「逐步執行負載」,這樣比較能模擬出較為真實的使用者人數變化:

給予壓力時有很多種混合測試的方法,組合可以非常多變,這裡我們可以選擇「按虛擬使用者人數」進行測試

如果你有設定多個測試情境的話,可以全部加進來,並依照自訂的比例進行分佈,例如「首頁-不動產買賣」為熱門功能,在所有測試中可以設定為 46%,而其他的測試情境也可以調整,如下圖示:

SNAGHTML2a09975b

你還可以依據不同的上網速度來設定,這樣更能模擬出使用者依據不同頻寬測試出不同的模擬情境。但這裡不一定要這麼做,壓力測試不可能執行一次就知道結果,一定會有多種不同的情境進行交叉分析才能得知網站在特定負載下是否能正常運作。

SNAGHTML2a0cca3d

當我們要測試穩定性時,建議設定負載測試持續時間,並指定適當的「準備持續時間」,因為網站剛開站必須先暖一暖身,先模擬 10 個人上來網站,將網站的部分功能執行過一次,爾後再開始執行壓測,這樣數據比較貼近真實的狀況。

完成後畫面如下:

執行壓力測試

這部份可參考胡百敬老師錄製的教學影片:VSTS2010效能及壓力測試 效能測試及壓力測試.zip

壓測報表分析

分析壓測報表也是一門學問,因為不同的欄位代表著不同的意思,還有些專有名稱必須事先理解後才知道到底報表在寫些什麼,重點在哪裡等等。在 Visual Studio 2012 Ultimate 測試控制器上,除了最後的結果報表外,壓測過程中還有許多非常具有價值的效能指標數據,這些都是從每一台電腦的「效能計數器」收錄進來的資料,分析這些數據對報表的真實度影響甚巨,不得不注意。

例如,當你在施加壓力的過程中,如果測試代理主機(VSAgent1)的 CPU 使用率已經高達 100% 的話,那麼也代表著測試代理主機(VSAgent1)已達效能瓶頸,無法再施加相對應的壓力到受測系統上,因此最後產生出來的報表數據也會失真,這點也是另一個常見的操作錯誤,並非所有主機都能模擬出 1,000 位虛擬使用者。

在此我列出 Visual Studio 2012 Ultimate 裡對於測試的層級與相對應的意義,不同層級的報表數據代表的意思有些微不同,因此必須釐清它們之間的階層關係:

第一層:測試回合 (代表完成一次負載測試結果,通常一次壓測案例或測試好多次)

第二層:情節 (一份測試回合包含多個情節定義)

第三層:測試 (一個情節通常混和著多個不同的測試案例)

第四層:頁面 (一個測試案例包含多個透過 HTTP 要求)

透過這些大量的數據,相信一定可以從報表中看出許多現有網站的各種問題,但這些分析的經驗很難在一篇文章中寫出,只能有空再寫了。

上線後追蹤與分析

沒有人會保證說經過壓力測試過的程式一定沒有問題,因此網站團隊應該持續追蹤網站上線後的流量變化與網站設備的負載狀況進行分析,如果網站無法負荷未預期的流量則需要進一步擴充硬體或調整程式。

剛看到一則〔實價登錄網今早重新上線 頻寬擴充4倍〕新聞提到該網站頻寬已拓寬至100Mb/s,同時間可容納2,000人次查詢,一天的查詢量可達12萬人次。我也順便對這個說法進行技術分析:

網站頻寬已拓寬至100Mb/s

應該先從機房的流量圖分析起,看是不是真的機房頻寬不足,如果是的話,提高網站頻寬才有意義,否則只是數字好看+亂花錢而已。

同時間可容納2,000人次查詢

這部份只要執行一次網站壓力測試就會得到最準確的數據,說不定測試過程中還會浮現更多沒有發現的問題,不然真的就只是個「說法」而已,建議可以在機房裡再做一次壓測,得到最正確的數字,以供後續參考。(我個人不相信這個數字是真的,因為下午每過幾分鐘網站就會掛掉一次,但線上人數才幾十人而已)

一天的查詢量可達12萬人次

這個數據基本上沒什麼意義,因為不會有很多人在深夜上這個網站查詢,一個網站的效能瓶頸都是出現在尖峰時刻,只要尖峰時刻可以負荷大量的查詢就夠了,其他時間基本上應該都能夠承受,至於一天的查詢量說 100 萬次應該也說的過去,這樣數字還比較好看。

總結

關於網站壓力測試這邊我整理幾條最基本的測試原則,供各位參考:

在測試網站壓力前,請先把網站的 Bugs 給清除乾淨,功能要先做確實,否則測試一個功能有問題的網站系統,以及不斷修修改改的網站,似乎沒什麼意義。再者,壓測過程中通常也會不斷修改網站程式進行效能優化,所以連著 Bugs 一起改程式,恐怕網站品質不會太高。

( 據說不動產交易實價查詢服務網進入查詢頁面前的驗證碼隨便打都會過,呵呵 )

徹底降低網路延遲的影響,才能測試出網站真實可承受之壓力。如果客戶的主機無法移動,那就把測試設備都搬進機房測試吧!

選對壓測工具,並徹底瞭解工具的使用與報表的數據。

壓測過程中要不斷收錄壓測過程中各個環節的數據,有量化的數據才能當成進行科學的分析,而不只是「感覺變快」而已,因為同時 100,000 人、同時 10,000 人在用與同時 10 個人在瀏覽網站時的差別非常大,這些不是用「感覺」可以衡量的。

結合多方專業與人才進行壓力測試,彼此貢獻專業技術與知識,不僅測出網站系統的瓶頸,還要能真正調出效能才是王道。

從未做過壓力測試的網站,先不急著做壓力測試,而是先分析出目前網站的效能瓶頸,得到基礎的效能數據後,再依據實際需求訂定壓測標準,否則盲目的設定標準只會讓報告不夠精準而已。

壓測完成後,應該持續追蹤網站上線後的流量變化,以進一步得到主機實際的負載狀況,如果到達需要擴充設備或調整程式效能時,也比較有個參考依據。

豆乾肉絲

豆乾肉絲(8人份)
材料
肉絲225公克
芹菜300公克
辣椒4條
豆乾600公克

調味料&醃料
醬油2大匙
太白粉適量

備料
1.豆乾橫切為3片,再縱切成絲,放在冷水裡泡1小時。
2.肉絲用適量太白粉和水抓拌一下。
3.芹菜切段。
4.紅辣椒去籽,切細絲。

烹飪
1.起油鍋(油可以略多一些),先炒肉絲到半熟,放下泡過水的豆乾(水要瀝乾),加入醬油,見到豆乾膨脹,下紅辣椒絲和芹菜,拌炒到熟,香氣透出就可以起鍋。
2.如果見鍋裡的湯汁多的話,起鍋前可以勾少許薄芡。

PHP的一些安全性問題及應對

1. SQL 注入攻擊 (SQL injection attack)

mysql_real_escape_string
2. 操縱 GET 的值 (manipulating GET variables)
is_numeric 、 regular expression(ereg(“^[0-9]+$")
3. 緩衝器溢位攻擊 (buffer overflow attack)
前端 maxlength 、strlen 、 substr
4. 跨站腳本攻擊 (cross-site scripting)
strip_tags 、 htmlspecialchars
5. 操縱瀏覽器內的資料 (manipulating data inside the browser)
假設Tamper Data
6. 遠程表格遞交 (remote form posting)
random token(md5、uniqid、rand)

轉載確保PHP安全,不能違反的四條安全規則網頁

確保PHP安全,不能違反的四條安全規則網頁 / 南方站長

規則 1:絕不要信任外部數據或輸入

關於 web 應用程序安全性,必須認識到的第一件事是不應該信任外部數據。外部數據(outside data) 包括不是由程序員在 php 代碼中直接輸入的任何數據。在採取措施確保安全之前,來自任何其他來源(比如 get 變量、表單 post、數據庫、配置文件、會話變量或 cookie)的任何數據都是不可信任的。

例如,下面的數據元素可以被認為是安全的,因為它們是在 php 中設置的。

清單 1. 安全無暇的代碼


<?php
$myusername = 'tmyer';
$arrayusers = array('tmyer', 'tom', 'tommy');
define("greeting", 'hello there' . $myusername);
?>

但是,下面的數據元素都是有瑕疵的。

清單 2. 不安全、有瑕疵的代碼


<?php
$myusername = $_post['username']; //tainted!
$arrayusers = array($myusername, 'tom', 'tommy'); //tainted!
define("greeting", 'hello there' . $myusername); //tainted!
?>

為 什麼第一個變量 $myusername 是有瑕疵的?因為它直接來自表單 post。用戶可以在這個輸入域中輸入任何字符串,包括用來清除文件或運行以前上傳的文件的惡意命令。您可能會問,「難道不能使用只接受字母 a-z 的客戶端(javascrīpt)表單檢驗腳本來避免這種危險嗎?」是的,這總是一個有好處的步驟,但是正如在後面會看到的,任何人都可以將任何表單下載 到自己的機器上,修改它,然後重新提交他們需要的任何內容。

解決方案很簡單:必須對 $_post[‘username’] 運行清理代碼。如果不這麼做,那麼在使用 $myusername 的任何其他時候(比如在數組或常量中),就可能污染這些對象。

對用戶輸入進行清理的一個簡單方法是,使用正則表達式來處理它。在這個示例中,只希望接受字母。將字符串限制為特定數量的字符,或者要求所有字母都是小寫的,這可能也是個好主意。

清單 3. 使用戶輸入變得安全


<?php
$myusername = cleaninput($_post['username']); //clean!
$arrayusers = array($myusername, 'tom', 'tommy'); //clean!
define("greeting", 'hello there' . $myusername); //clean!
function cleaninput($input){
$clean = strtbower($input);
$clean = preg_replace("/[^a-z]/", "", $clean);
$clean = substr($clean,0,12);
return $clean;
}
?>

規則 2:禁用那些使安全性難以實施的 php 設置

已經知道了不能信任用戶輸入,還應該知道不應該信任機器上配置 php 的方式。例如,要確保禁用 register_globals。如果啟用了 register_globals,就可能做一些粗心的事情,比如使用 $variable 替換同名的 get 或 post 字符串。通過禁用這個設置,php 強迫您在正確的名稱空間中引用正確的變量。要使用來自表單 post 的變量,應該引用 $_post[‘variable’]。這樣就不會將這個特定變量誤會成 cookie、會話或 get 變量。

規則 3:如果不能理解它,就不能保護它

一些開發人員使用奇怪的語法,或者將語句組織得很緊湊,形成簡短但是含義模糊的代碼。這種方式可能效率高,但是如果您不理解代碼正在做什麼,那麼就無法決定如何保護它。

例如,您喜歡下面兩段代碼中的哪一段?

清單 4. 使代碼容易得到保護


<?php
//obfuscated code
$input = (isset($_post['username']) ? $_post['username']:'');
//unobfuscated code
$input = '';
if (isset($_post['username'])){
$input = $_post['username'];
}else{
$input = '';
}
?>

在第二個比較清晰的代碼段中,很容易看出 $input 是有瑕疵的,需要進行清理,然後才能安全地處理。

規則 4:「縱深防禦」 是新的法寶

本教程將用示例來說明如何保護在線表單,同時在處理表單的 php 代碼中採用必要的措施。同樣,即使使用 php regex 來確保 get 變量完全是數字的,仍然可以採取措施確保 sql 查詢使用轉義的用戶輸入。

縱深防禦不只是一種好思想,它可以確保您不會陷入嚴重的麻煩。

既然已經討論了基本規則,現在就來研究第一種威脅:sql 注入攻擊。

防止 sql 注入攻擊

在 sql 注入攻擊 中,用戶通過操縱表單或 get 查詢字符串,將信息添加到數據庫查詢中。例如,假設有一個簡單的登錄數據庫。這個數據庫中的每個記錄都有一個用戶名字段和一個密碼字段。構建一個登錄表單,讓用戶能夠登錄。

清單 5. 簡單的登錄表單

<html>
< head>
< title>login</title>
< /head>
< body>
< form action="verify.php" method="post">
< p><label for=’user’>username</label>
< input type=’text’ name=’user’ id=’user’/>
< /p>
< p><label for=’pw’>password</label>
< input type=’password’ name=’pw’ id=’pw’/>
< /p>
< p><input type=’submit’ value=’login’/></p>
< /form>
< /body>
< /html>

這個表單接受用戶輸入的用戶名和密碼,並將用戶輸入提交給名為 verify.php 的文件。在這個文件中,php 處理來自登錄表單的數據,如下所示:

清單 6. 不安全的 php 表單處理代碼


<?php
$okay = 0;
$username = $_post['user'];
$pw = $_post['pw'];
$sql = "select count(*) as ctr from users where username='".$username."' and password='". $pw."' limit 1";
$result = mysql_query($sql);
while ($data = mysql_fetch_object($result)){
if ($data->ctr == 1){
//they're okay to enter the application!
$okay = 1;
}
}
if ($okay){
$_session['loginokay'] = true;
header("index.php");
}else{
header("login.php");
}
?>

這 段代碼看起來沒問題,對嗎?世界各地成百(甚至成千)的 php/mysql 站點都在使用這樣的代碼。它錯在哪裡?好,記住 「不能信任用戶輸入」。這裡沒有對來自用戶的任何信息進行轉義,因此使應用程序容易受到攻擊。具體來說,可能會出現任何類型的 sql 注入攻擊。

例如,如果用戶輸入 foo 作為用戶名,輸入 ‘ or ‘1’=’1 作為密碼,那麼實際上會將以下字符串傳遞給 php,然後將查詢傳遞給 mysql:


<?php
$sql = "select count(*) as ctr  from users where username='foo' and password='' or '1'='1' limit 1";
?>

這個查詢總是返回計數值 1,因此 php 會允許進行訪問。通過在密碼字符串的末尾注入某些惡意 sql,黑客就能裝扮成合法的用戶。

解 決這個問題的辦法是,將 php 的內置 mysql_real_escape_string() 函數用作任何用戶輸入的包裝器。這個函數對字符串中的字符進行轉義,使字符串不可能傳遞撇號等特殊字符並讓 mysql 根據特殊字符進行操作。清單 7 展示了帶轉義處理的代碼。

清單 7. 安全的 php 表單處理代碼


<?php
$okay = 0;
$username = $_post['user'];
$pw = $_post['pw'];
$sql = "select count(*) as ctr from users where username='".mysql_real_escape_string($username)."' and password='". mysql_real_escape_string($pw)."' limit 1";
$result = mysql_query($sql);
while ($data = mysql_fetch_object($result)){
if ($data->ctr == 1){
//they're okay to enter the application!
$okay = 1;
}
}
if ($okay){
$_session['loginokay'] = true;
header("index.php");
}else{
header("login.php");
}
?>

使用 mysql_real_escape_string() 作為用戶輸入的包裝器,就可以避免用戶輸入中的任何惡意 sql 注入。如果用戶嘗試通過 sql 注入傳遞畸形的密碼,那麼會將以下查詢傳遞給數據庫:

select count(*) as ctr from users where username=’foo’ and password=’\’ or \’1\’=\’1′ limit 1″

數據庫中沒有任何東西與這樣的密碼匹配。僅僅採用一個簡單的步驟,就堵住了 web 應用程序中的一個大漏洞。這裡得出的經驗是,總是應該對 sql 查詢的用戶輸入進行轉義。

但是,還有幾個安全漏洞需要堵住。下一項是操縱 get 變量。

防止用戶操縱 get 變量

在前一節中,防止了用戶使用畸形的密碼進行登錄。如果您很聰明,應該應用您學到的方法,確保對 sql 語句的所有用戶輸入進行轉義。

但 是,用戶現在已經安全地登錄了。用戶擁有有效的密碼,並不意味著他將按照規則行事 —— 他有很多機會能夠造成損害。例如,應用程序可能允許用戶查看特殊的內容。所有鏈接指向 tbplate.php?pid=33 或 tbplate.php?pid=321 這樣的位置。url 中問號後面的部分稱為查詢字符串。因為查詢字符串直接放在 url 中,所以也稱為 get 查詢字符串。

在 php 中,如果禁用了 register_globals,那麼可以用 $_get[‘pid’] 訪問這個字符串。在 tbplate.php 頁面中,可能會執行與清單 8 相似的操作。

清單 8. 示例 tbplate.php


<?php
$pid = $_get['pid'];
//we create an object of a fictional class page
$obj = new page;
$content = $obj->fetchpage($pid);
//and now we have a bunch of php that displays the page
?>

這 裡有什麼錯嗎?首先,這裡隱含地相信來自瀏覽器的 get 變量 pid 是安全的。這會怎麼樣呢?大多數用戶沒那麼聰明,無法構造出語義攻擊。但是,如果他們注意到瀏覽器的 url 位置域中的 pid=33,就可能開始搗亂。如果他們輸入另一個數字,那麼可能沒問題;但是如果輸入別的東西,比如輸入 sql 命令或某個文件的名稱(比如 /etc/passwd),或者搞別的惡作劇,比如輸入長達 3,000 個字符的數值,那麼會發生什麼呢?

在這種情況下,要記住基本規則,不要信任用戶輸入。應用程序開發人員知道 tbplate.php 接受的個人標識符(pid)應該是數字,所以可以使用 php 的 is_numeric() 函數確保不接受非數字的 pid,如下所示:

清單 9. 使用 is_numeric() 來限制 get 變量


<?php
$pid = $_get['pid'];
if (is_numeric($pid)){
//we create an object of a fictional class page
$obj = new page;
$content = $obj->fetchpage($pid);
//and now we have a bunch of php that displays the page
}else{
//didn't pass the is_numeric() test, do something else!
}
?>

這個方法似乎是有效的,但是以下這些輸入都能夠輕鬆地通過 is_numeric() 的檢查:

100 (有效)
100.1 (不應該有小數位)
+0123.45e6 (科學計數法 —— 不好)
0xff33669f (十六進制 —— 危險!危險!)

那麼,有安全意識的 php 開發人員應該怎麼做呢?多年的經驗表明,最好的做法是使用正則表達式來確保整個 get 變量由數字組成,如下所示:

清單 10. 使用正則表達式限制 get 變量


<?php
$pid = $_get['pid'];
if (strlen($pid)){
if (!ereg("^[0-9]+$",$pid)){
//do something appropriate, like maybe logging thb out or sending thb back to home page
}
}else{
//bpty $pid, so send thb back to the home page
}
//we create an object of a fictional class page, which is now
//moderately protected from evil user input
$obj = new page;
$content = $obj->fetchpage($pid);
//and now we have a bunch of php that displays the page
?>

需 要做的只是使用 strlen() 檢查變量的長度是否非零;如果是,就使用一個全數字正則表達式來確保數據元素是有效的。如果 pid 包含字母、斜線、點號或任何與十六進制相似的內容,那麼這個例程捕獲它並將頁面從用戶活動中屏蔽。如果看一下 page 類幕後的情況,就會看到有安全意識的 php 開發人員已經對用戶輸入 $pid 進行了轉義,從而保護了 fetchpage() 方法,如下所示:

清單 11. 對 fetchpage() 方法進行轉義


<?php
class page{
function fetchpage($pid){
$sql = "select pid,title,desc,kw,content,status from page where pid='".mysql_real_escape_string($pid)."'";
}
}
?>

您可能會問,「既然已經確保 pid 是數字,那麼為什麼還要進行轉義?」 因為不知道在多少不同的上下文和情況中會使用 fetchpage() 方法。必須在調用這個方法的所有地方進行保護,而方法中的轉義體現了縱深防禦的意義。

如 果用戶嘗試輸入非常長的數值,比如長達 1000 個字符,試圖發起緩衝區溢出攻擊,那麼會發生什麼呢?下一節更詳細地討論這個問題,但是目前可以添加另一個檢查,確保輸入的 pid 具有正確的長度。您知道數據庫的 pid 字段的最大長度是 5 位,所以可以添加下面的檢查。

清單 12. 使用正則表達式和長度檢查來限制 get 變量


<?php
$pid = $_get['pid'];
if (strlen($pid)){
if (!ereg("^[0-9]+$",$pid) && strlen($pid) > 5){
//do something appropriate, like maybe logging thb out or sending thb back to home page
}
} else {
//bpty $pid, so send thb back to the home page
}
//we create an object of a fictional class page, which is now
//even more protected from evil user input
$obj = new page;
$content = $obj->fetchpage($pid);
//and now we have a bunch of php that displays the page
?>

現在,任何人都無法在數據庫應用程序中塞進一個 5,000 位的數值 —— 至少在涉及 get 字符串的地方不會有這種情況。想像一下黑客在試圖突破您的應用程序而遭到挫折時咬牙切齒的樣子吧!而且因為關閉了錯誤報告,黑客更難進行偵察。

緩衝區溢出攻擊

緩衝區溢出攻擊 試圖使 php 應用程序中(或者更精確地說,在 apache 或底層操作系統中)的內存分配緩衝區發生溢出。請記住,您可能是使用 php 這樣的高級語言來編寫 web 應用程序,但是最終還是要調用 c(在 apache 的情況下)。與大多數低級語言一樣,c 對於內存分配有嚴格的規則。

緩衝區溢出攻擊向緩衝區發送大量數據,使部分數據溢出到相鄰的內存緩衝區,從而破壞緩衝區或者重寫邏輯。這樣就能夠造成拒絕服務、破壞數據或者在遠程服務器上執行惡意代碼。

防止緩衝區溢出攻擊的惟一方法是檢查所有用戶輸入的長度。例如,如果有一個表單元素要求輸入用戶的名字,那麼在這個域上添加值為 40 的 maxlength 屬性,並在後端使用 substr() 進行檢查。清單 13 給出表單和 php 代碼的簡短示例。

清單 13. 檢查用戶輸入的長度


<?php
if ($_post['submit'] == "go"){
$name = substr($_post['name'],0,40);
}
?>

<form action="


<?phpecho $_server['php_self']; ?>

" method="post">
< p><label for="name">name</label>
< input type="text" name="name" size="20″ maxlength="40″/></p>
< p><input type="submit" name="submit" value="go"/></p>
< /form>

為 什麼既提供 maxlength 屬性,又在後端進行 substr() 檢查?因為縱深防禦總是好的。瀏覽器防止用戶輸入 php 或 mysql 不能安全地處理的超長字符串(想像一下有人試圖輸入長達 1,000 個字符的名稱),而後端 php 檢查會確保沒有人遠程地或者在瀏覽器中操縱表單數據。

正如您看到的,這種方式與前一節中使用 strlen() 檢查 get 變量 pid 的長度相似。在這個示例中,忽略長度超過 5 位的任何輸入值,但是也可以很容易地將值截短到適當的長度,如下所示:

清單 14. 改變輸入的 get 變量的長度


<?php
$pid = $_get['pid'];
if (strlen($pid)){
if (!ereg("^[0-9]+$",$pid)){
//if non numeric $pid, send thb back to home page
}
}else{
//bpty $pid, so send thb back to the home page
}
//we have a numeric pid, but it may be too long, so let's check
if (strlen($pid)>5){
$pid = substr($pid,0,5);
}
//we create an object of a fictional class page, which is now
//even more protected from evil user input
$obj = new page;
$content = $obj->fetchpage($pid);
//and now we have a bunch of php that displays the page
?>

注 意,緩衝區溢出攻擊並不限於長的數字串或字母串。也可能會看到長的十六進制字符串(往往看起來像 \xa3 或 \xff)。記住,任何緩衝區溢出攻擊的目的都是淹沒特定的緩衝區,並將惡意代碼或指令放到下一個緩衝區中,從而破壞數據或執行惡意代碼。對付十六進制緩 沖區溢出最簡單的方法也是不允許輸入超過特定的長度。

如果您處理的是允許在數據庫中輸入較長條目的表單文本區,那麼無法在客戶端輕鬆地限制數據的長度。在數據到達 php 之後,可以使用正則表達式清除任何像十六進制的字符串。

清單 15. 防止十六進制字符串


<?php
if ($_post['submit'] == "go"){
$name = substr($_post['name'],0,40);
//clean out any potential hexadecimal characters
$name = cleanhex($name);
//continue processing....
}
function cleanhex($input){
$clean = preg_replace("![][xx]([a-fa-f0-9]{1,3})!", "",$input);
return $clean;
}
?>

<form action="


<?phpecho $_server['php_self']; ?>

" method="post">
< p><label for="name">name</label>
< input type="text" name="name" size="20″ maxlength="40″/></p>
< p><input type="submit" name="submit" value="go"/></p>
< /form>

您 可能會發現這一系列操作有點兒太嚴格了。畢竟,十六進制串有合法的用途,比如輸出外語中的字符。如何部署十六進制 regex 由您自己決定。比較好的策略是,只有在一行中包含過多十六進制串時,或者字符串的字符超過特定數量(比如 128 或 255)時,才刪除十六進制串。

跨站點腳本攻擊

在跨站點腳本(xss)攻擊中,往往有一個惡意用戶在表單中(或通過其他用戶輸入方式)輸入信息,這些輸入將惡 意的客戶端標記插入過程或數據庫中。例如,假設站點上有一個簡單的來客登記簿程序,讓訪問者能夠留下姓名、電子郵件地址和簡短的消息。惡意用戶可以利用這 個機會插入簡短消息之外的東西,比如對於其他用戶不合適的圖片或將用戶重定向到另一個站點的 javascrīpt,或者竊取 cookie 信息。

幸運的是,php 提供了 strip_tags() 函數,這個函數可以清除任何包圍在 html 標記中的內容。strip_tags() 函數還允許提供允許標記的列表,比如 <b> 或 <i>。

瀏覽器內的數據操縱

有一類瀏覽器插件允許用戶篡改頁面上的頭部元素和表單元素。使用 tamper data(一個 mozilla 插件),可以很容易地操縱包含許多隱藏文本字段的簡單表單,從而向 php 和 mysql 發送指令。

用戶在點擊表單上的 submit 之前,他可以啟動 tamper data。在提交表單時,他會看到表單數據字段的列表。tamper data 允許用戶篡改這些數據,然後瀏覽器完成表單提交。

讓我們回到前面建立的示例。已經檢查了字符串長度、清除了 html 標記並刪除了十六進制字符。但是,添加了一些隱藏的文本字段,如下所示:

清單 17. 隱藏變量


<?php
if ($_post['submit'] == "go"){
//strip_tags
$name = strip_tags($_post['name']);
$name = substr($name,0,40);
//clean out any potential hexadecimal characters
$name = cleanhex($name);
//continue processing....
}
function cleanhex($input){
$clean = preg_replace("![][xx]([a-fa-f0-9]{1,3})!", "",$input);
return $clean;
}
?>

<form action="


<?phpecho $_server['php_self']; ?>

" method="post">
< p><label for="name">name</label>
< input type="text" name="name" size="20″ maxlength="40″/></p>
< input type="hidden" name="table" value="users"/>
< input type="hidden" name="action" value="create"/>
< input type="hidden" name="status" value="live\"/>
< p><input type="submit" name="submit" value="go"/></p>
< /form>

注意,隱藏變量之一暴露了表名:users。還會看到一個值為 create 的 action 字段。只要有基本的 sql 經驗,就能夠看出這些命令可能控制著中間件中的一個 sql 引擎。想搞大破壞的人只需改變表名或提供另一個選項,比如 delete。

現在還剩下什麼問題呢?遠程表單提交。

遠程表單提交

web 的好處是可以分享信息和服務。壞處也是可以分享信息和服務,因為有些人做事毫無顧忌。

以 表單為例。任何人都能夠訪問一個 web 站點,並使用瀏覽器上的 file > save as 建立表單的本地副本。然後,他可以修改 action 參數來指向一個完全限定的 url(不指向 formhandler.php,而是指向 http://www.yoursite.com/formhandler.php,因為表單在這個站點上),做他希望的任何修改,點擊 submit,服務器會把這個表單數據作為合法通信流接收。

首先可能考慮檢查 $_server[‘http_referer’],從而判斷請求是否來自自己的服務器,這種方法可以擋住大多數惡意用戶,但是擋不住最高明的黑客。這些人足夠聰明,能夠篡改頭部中的引用者信息,使表單的遠程副本看起來像是從您的服務器提交的。

處理遠程表單提交更好的方式是,根據一個惟一的字符串或時間戳生成一個令牌,並將這個令牌放在會話變量和表單中。提交表單之後,檢查兩個令牌是否匹配。如果不匹配,就知道有人試圖從表單的遠程副本發送數據。

要創建隨機的令牌,可以使用 php 內置的 md5()、uniqid() 和 rand() 函數,如下所示:

清單 18. 防禦遠程表單提交


<?php
session_start();
if ($_post['submit'] == "go"){
//check token
if ($_post['token'] == $_session['token']){
//strip_tags
$name = strip_tags($_post['name']);
$name = substr($name,0,40);
//clean out any potential hexadecimal characters
$name = cleanhex($name);
//continue processing....
}else{
//stop all processing! rbote form posting attbpt!
}
}
$token = md5(uniqid(rand(), true));
$_session['token']= $token;
function cleanhex($input){
$clean = preg_replace("![][xx]([a-fa-f0-9]{1,3})!", "",$input);
return $clean;
}
?>

<form action="


<?phpecho $_server['php_self']; ?>

" method="post">
< p><label for="name">name</label>
< input type="text" name="name" size="20″ maxlength="40″/></p>
< input type="hidden" name="token" value="


<?phpecho $token; ?>

“/>
< p><input type="submit" name="submit" value="go"/></p>
< /form>

這種技術是有效的,這是因為在 php 中會話數據無法在服務器之間遷移。即使有人獲得了您的 php 源代碼,將它轉移到自己的服務器上,並向您的服務器提交信息,您的服務器接收的也只是空的或畸形的會話令牌和原來提供的表單令牌。它們不匹配,遠程表單提交就失敗了。

轉載如何開發夠安全的PHP網頁

如何開發夠安全的PHP網頁? – iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天
https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js
https://oss.maxcdn.com/respond/1.4.2/respond.min.js

用PHP撰寫網頁時,該注意哪些寫法,避免安全性產生問題?
首先要找出前端網頁中允許使用者輸入的部分,像是表單標籤內允許輸入的欄位或選單部分,這些允許使用者輸入的部分,表示使用者可以自行決定要輸入的,正常的內容當然是最好,但也可能是超出允許範圍之外的內容,甚至是帶有惡意的程式碼,這通常也是嘗試入侵時會被利用的點。一般常見的例子像是使用者登入時的帳號密碼欄位等。此外,許多動態產生的連結URL也是容易被利用的輸入點,例如:http://www.example.com/query.php?qid=13313&uid=31331&page=2其中qid、uid、page都是有機會讓使用者修改輸入的輸入點,而在PHP中會以$_REQUEST、$_POST、$_GET等參數傳入網站應用系統,後續再處理與應用。

設變數接受輸入
一般來說,程式要使用外部輸入的資料時,要避免直接使用$_REQUEST、$_POST、$_GET這些原始輸入,較安全的方法通常是先設個變數接收,然後依據變數的用途,執行必要的檢查與過濾後再使用,如此一來,只要程式設計師養成好習慣,例如︰「不使用原始輸入資料」、「輸入資料過濾檢查後再使用」,效果就像在網路上部署防火牆一樣,能將外部資料與內部的資料處理流程區隔開來,並檢查與過濾進入內部的所有資料,符合條件者才准傳入。這是建立「輸入驗證」防線的基礎原則。

限制輸入環境
輸入環境的限制是一種避免出現非預期輸入的方法。如果有明確的輸入選項,就不要提供文字輸入欄位,例如性別在絕大部分情況下,只有「男」與「女」兩個選擇,就不要提供使用者有輸入「石頭」的機會;一年不會超過12個月,每個月都有固定的天數,這些最好都做成選單讓使用者選擇。

就算不是選項型態,多半也有固定的格式,也因此會有固定的條件限制,例如信用卡號碼為16碼的數字,就不會有英文、數字混雜的情況;像身份證字號本身有特定的編碼方式,也可以使用公式驗證;電子郵件帳號一定要「@」,這些都是很實際的例子。

利用白名單選項,也是一種做法。限制輸入的可能性,或限制輸入的格式、型態、長度、內容等,只要條件越明確,就越能避免有非預期的輸入。通常網頁程式設計師會利用JavaScript與正規表示式(Regular Expression)預先檢查輸入的內容,不過,JavaScript的限制功能多半很容易就被繞過或是關閉,只能「防君子不防小人」,還是要在伺服器端對這些輸入再驗證一次,比較保險。

在輸入過濾檢查工作前,仍需要先瞭解資料要輸入的環境,例如會在哪些環境下使用,這樣才知道後面需要設定什麼過濾檢查的條件。舉例來說,如果我們知道變數$var將要傳入SQL語法中當作關鍵字查詢,我們才會知道除了要檢查與過濾$var內容的合理性之外,還需要考慮$var在SQL語法中的限制,這樣對$var的過濾檢查才會完整;如果$var會傳進system(),作為Linux執行指令的一部份,就要考慮$var會對Linux指令造成的影響而預先過濾,這時的過濾條件又跟之前用在SQL語法時是不同的。

防止SQL Injection大部分開發者都會應用 addslashes() 來防止SQL Injection的發生,但addslashes()只會過濾單引號(’)、雙引號(")、反斜線 backslash(\) 以及空字元 NUL((the null byte,特殊攻擊仍有辦法跳過檢查。而php.ini中的magic_quotes_gpc選項雖然能夠在$_GET,$_POST,$_COOKIE這三種輸入執行過濾,但因為動作與addslashes()雷同,如前所述,一樣有被跳脫的疑慮。
所以,我們建議採用資料庫提供的過濾函式,譬如MySQL可以使用mysql_real_escape_string()濾除跳脫字元,也免除了針對每個輸入都加上過濾函式的繁瑣工作。

如果PHP程式中有呼叫系統指令,則可以使用escapeshellcmd()過濾Shell內的一些有意義的特殊字元,或是乾脆使用escapeshellarg(),將所有可能的輸入參數都濾除,這樣可以防止Command Injection的發生。

當輸入資料的用途是指出系統內的某個或某些檔案時,例如應用在require()、require_once()等,應該限制僅允許輸入檔名,將目錄寫死或設定絕對路徑,也可以將「../」這樣的目錄跳脫濾除,來避免Code Injection的問題。

輸出、輸入都要檢查,防XSS
內容輸出前,也需要進行過濾檢查,避免插入了非預期的網頁內容、修改網頁呈現,甚至插入惡意的JavaScript,也就是Cross-Site Scripting(XSS)。在PHP中可以使用htmlspecialchars()將 & > " < 轉成HTML字串格式,例如:
* & (和) 轉成 &
* " (雙引號) 轉成 “
* < (小於) 轉成 <
* > (大於) 轉成 >
這樣可以避免使用者的輸入中當有、這些HTML標籤時會被瀏覽器當成網頁內容而執行。如果資料輸入時本就不允許輸入HTML標籤,不如直接在輸入檢查時,以strip_tag()直接濾掉,免除後患。

總而言之,完善的輸入驗證(Input Validation)可以預防大部分的網頁資安問題,而完善的輸出驗證(Output Validation)則可以防止OWASP 2007 Top 10排名第一的XSS攻擊。做好這些輸出入驗證工作,便可以有效強化PHP網頁程式的安全性。

久站 養生術

久站 養生術!不易痠麻、腫脹 – 康健雜誌59期

你是

族嗎?許多工作性質必須經常站立,像是老師、護士、空姐、專櫃小姐或憲兵,別以為

了頂多小腿變粗而已,如果站立時姿勢不當,很容易導致靜脈曲張,甚至膝蓋變形。

原因是,當身體直,膝蓋繃緊,換成小腿出力時,氣血就不易通暢,「因為血管神經被壓縮,而腿部屬於末梢,血液不易回流,」中華民國科學氣功學會理事長吳長新解釋,當下肢血液無法順利回流,即逐漸產生腳麻、痠痛現象,嚴重的還會膝蓋腫大、老化。

我們提供幾個站立時該注意的原則,以及幾招簡易養生保健功法,讓下肢氣血更活絡通暢。

平常站立時的簡單原則:

膝蓋微微彎曲,好像彎曲又好像沒有彎曲,這就是放鬆。切記,如果必須很,千萬不要繃緊膝蓋。

左右腳互換,重心不要一直擺在某個膝蓋或某隻腳上。不斷更換重心,5∼10分鐘左右腳互換。

轉腰部,用腰部畫圓圈,腰放鬆,腳也跟著放鬆。所有下肢神經都得通過腰部,長期容易腰痠、腰痛。轉轉腰部可活絡氣血循環。

養生保健運動

A.開六關(指兩腳的踝、膝和髖關節)

兩腳張開站立,將身體重心放在一隻腳上,另外一隻腳放鬆,腳跟稍微抬高,以膝蓋做,向外轉9圈,再往內轉9圈。接著換另一隻腳。

開六關的動作能將腳踝、膝、髖關節全運動到,整隻腳都鬆了。

B.吐牛轉膝

將兩手放在膝蓋上,頭抬高,先右轉膝蓋9圈,再左轉9圈,如此左右交替幾次。

C.穴道按摩

1.陽陵泉(主筋,屬膽經):用脛骨粗隆和腓骨小頭做兩點(位置圖請參考圖示),中間連線,畫一個正三角形,頂點即陽陵泉(圖中食指處)。

用手指指節頂揉,會有痠痛感,即是此處。

可強化筋的功能。

2.復溜(主骨,屬腎經):

踝骨,內踝尖後面有個凹陷處,上來約兩個橫指頭的寬度(圖中食指處),即復溜穴,用大拇指腹輕輕壓揉。