關于
XSS 怎樣形成、如何注入、能做什么、如何防范,前人已有無數的探討,這里就不再累述了。本文介紹的則是另一種預防思路。
幾乎每篇談論 XSS 的
文章,結尾多少都會提到如何防止,然而大多萬變不離其宗。要轉義什么,要過濾什么,不要忘了什么之類的。盡管都是眾所周知的道理,但 XSS 漏洞十幾年來幾乎從未中斷過,不乏一些大網站也時常爆出,小網站更是家常便飯。
預警系統
事實上,至今仍未有一勞永逸的解決方案,要避免它依舊使用最古老的土辦法,逐個的過濾。然而人總有疏忽的時候,每當產品迭代更新時,難免會遺漏一些新字段,導致漏洞被引入。
即使圣人千慮也有一失,程序出 BUG 完全可以理解,及時修復就行。但令人費解的是,問題出現到被發現,卻要經過相當長的時間。例如不久前貼吧 XSS 蠕蟲腳本,直到大規模爆發后經用戶舉報,最終才得知。其他網站大多也類似,直到白帽子們挖掘出漏洞,提交到安全平臺上,最終廠商才被告知。若遇到黑客私下留著這些漏洞慢慢利用,那只能聽天由命了。
因此,要是能有一套實時的預警系統,那就更好了。即使無法阻止漏洞的發生,但能在漏洞觸發的第一時間里,通知開發人員,即可在最短的時間里修復,將損失降到最低。各式各樣的應用層防火墻,也由此產生。
不過,和傳統的系統漏洞不同,XSS 最終是在用戶頁面中觸發的。因此,我們不妨嘗試使用前端的思路,進行在線防御。
DOM 儲存型 XSS
先來假設一個有 BUG 的后臺,沒有很好處理用戶輸入的數據,導致 XSS 能被注入到頁面:
<img src="{路徑}" />
<img src="{路徑" onload="alert(/xss/)}" />
只轉義尖括號,卻忘了引號,是 XSS 里最為常見的。攻擊者們可以提前關閉屬性,并添加一個極易觸發的內聯事件,跨站腳本就這樣被輕易執行了。
那么,我們能否使用前端腳本來捕獲,甚至攔截呢?
被動掃描
最簡單的辦法,就是把頁面里所有元素都掃描一遍,檢測那些 on 開頭的內聯屬性,看看是不是存在異常:
例如字符數非常多,正常情況下這是很少出現的,但 XSS 為了躲避轉義有時會編碼的很長;例如出現一些 XSS 經常使用的關鍵字,但在實際產品里幾乎不會用到的。這些都可以作為漏洞出現的征兆,通知給開發人員。
不過,土辦法終究存在很大的局限性。在如今清一色的 AJAX 時代,頁面元素從來都不是固定的。伴隨著用戶各種交互,新內容隨時都可能動態添加進來。即使換成定期掃描一次,XSS 也可能在定時器的間隔中觸發,并銷毀自己,那樣永遠都無法跟蹤到了。況且,頻繁的掃描對性能影響也是巨大的。
如同早期的安全軟件一樣,每隔幾秒掃描一次注冊表啟動項,不僅費性能,而且對惡意軟件幾乎不起作用;但之后的主動防御系統就不同了,只有在真正調用 API 時才進行分析,不通過則直接攔截,完全避免了定時器的間隔遺漏。
因此,我們需要這種類似的延時策略 —— 僅在 XSS 即將觸發時對其分析,對不符合策略的元素,進行攔截或者放行,同時發送報警到后臺日志。
主動防御
『主動防御』,這概念放在前端腳本里似乎有些玄乎。但不難發現,這僅僅是執行優先級的事而已 —— 只要防御程序能運行在其他程序之前,我們就有了可進可退的主動權。對于無比強大的 HTML5 和靈活多變的 JavaScript,這些概念都可以被玩轉出來。
繼續回到剛才討論的內聯事件 XSS 上來。瀏覽器雖然沒提供可操控內聯事件的接口,但內聯事件的本質仍是一個事件,無論怎樣變化都離不開 DOM 事件模型。
扯到模型上面,一切即將迎刃而解。模型是解決問題的最靠譜的辦法,尤其是像 DOM-3-Event 這種早已制定的模型,其穩定性毋庸置疑。
即便沒仔細閱讀官方文檔,但凡做過網頁的都知道,有個 addEventListener 的接口,并取代了曾經一個古老的叫 attachEvent 的東西。盡管只是新增了一個參數而已,但正是這個差別成了人們津津樂道的話題。每當
面試談到事件時,總少不了考察下這個新參數的用途。盡管在日常開發中很少用到它。
關于事件捕獲和冒泡的細節,就不多討論了。下面的這段代碼,或許能激發你對『主動防御』的遐想。
<button onclick="console.log('target')">CLICK ME</button> <script> document.addEventListener('click', function(e) { console.log('bubble'); }); document.addEventListener('click', function(e) { console.log('capture'); //e.stopImmediatePropagation(); }, true); </script> Run |
盡管按鈕上直接綁了一個內聯的事件,但事件模型并不買賬,仍然得按標準的流程走一遍。capture,target,bubble,模型就是那樣固執。
不過,把那行注釋的代碼恢復,結果就只剩 capture 了。這個簡單的道理大家都明白,也沒什么好解釋的。
但仔細揣摩下,這不就是『主動防御』的概念嗎?捕獲程序運行在內聯事件觸發之前,并且完全有能力攔截之后的調用。
上面的 Demo 只是不假思索攔截了所有的事件。如果我們再加一些策略判斷,或許就更明朗了:
<button onclick="console.log('xss')">CLICK ME</button> <script> document.addEventListener('click', function(e) { console.log('bubble'); }); document.addEventListener('click', function(e) { var element = e.target; var code = element.getAttribute('onclick'); if (/xss/.test(code)) { e.stopImmediatePropagation(); console.log('攔截可疑事件:', code); } }, true); </script> Run |
我們先在捕獲階段掃描內聯事件字符,若是出現了『xss』這個關鍵字,后續的事件就被攔截了;換成其他字符,仍然繼續執行。同理,我們還可以判斷字符長度是否過多,以及更詳細的黑白名單正則。
怎么樣,一個主動防御的原型誕生了吧。
不過,上面的片段還有個小問題,就是把事件的冒泡過程也給屏蔽了,而我們僅僅想攔截內聯事件而已。解決辦法也很簡單,把 e.stopImmediatePropagation() 換成 element.onclick = null 就可以了。
當然,目前這只能防護 onclick,而現實中有太多的內聯事件。鼠標、鍵盤、觸屏、網絡狀態等等,不同瀏覽器支持的事件也不一樣,甚至還有私有事件,難道都要事先逐一列出并且都捕獲嗎?是的,可以都捕獲,但不必事先都列出來。
因為我們監聽的是 document 對象,瀏覽器所有內聯事件都對應著 document.onxxx 的屬性,因此只需運行時遍歷一下 document 對象,即可獲得所有的事件名。
<img src="*" onerror="console.log('xss')" />
<script> function hookEvent(onevent) { document.addEventListener(onevent.substr(2), function(e) { var element = e.target; if (element.nodeType != Node.ELEMENT_NODE) { return; } var code = element.getAttribute(onevent); if (code && /xss/.test(code)) { element[onevent] = null; console.log('攔截可疑事件:', code); } }, true); } console.time('耗時'); for (var k in document) { if (/^on/.test(k)) { //console.log('監控:', k); hookEvent(k); } } console.timeEnd('耗時'); </script> Run |
現在,無論頁面中哪個元素觸發哪個內聯事件,都能預先被我們捕獲,并根據策略可進可退了。
性能優化
或許有些事件沒有必要捕獲,例如視頻播放、音量調節等,但就算全都捕捉也耗不了多少時間,基本都在 1ms 左右。
當然,注冊事件本來就花不了多少時間,真正的耗費都算在回調上了。盡管大多數事件觸發都不頻繁,額外的掃描可以忽律不計。但和鼠標移動相關的事件那就不容忽視了,因此得考慮性能優化。
顯然,內聯事件代碼在運行過程中幾乎不可能發生變化。使用內聯事件大多為了簡單,如果還要在運行時 setAttribute 去改變內聯代碼,完全就是不可理喻的。因此,我們只需對某個元素的特定事件,掃描一次就可以了。之后根據標志,即可直接跳過。
<div style="width:100%; height:100%; position:absolute" onmouseover="console.log('xss')"></div> <script> function hookEvent(onevent) { document.addEventListener(onevent.substr(2), function(e) { var element = e.target; // 跳過已掃描的事件 var flags = element['_flag']; if (!flags) { flags = element['_flag'] = {}; } if (typeof flags[onevent] != 'undefined') { return; } flags[onevent] = true; if (element.nodeType != Node.ELEMENT_NODE) { return; } var code = element.getAttribute(onevent); if (code && /xss/.test(code)) { element[onevent] = null; console.log('攔截可疑代碼:', code); } }, true); } for (var k in document) { if (/^on/.test(k)) { hookEvent(k); } } </script> Run |
這樣,之后的掃描僅僅是判斷一下目標對象中的標記而已。即使瘋狂晃動鼠標,CPU 使用率也都忽略不計了。
到此,在 XSS 內聯事件這塊,我們已實現主動防御。
對于有著大量字符,或者出現類似 String.fromCharCode,$.getScript 這類典型 XSS 代碼的,完全可以將其攔截;發現有 alert(/xss/),alert(123) 這些測試代碼,可以暫時放行,并將日志發送到后臺,確定是否能夠復現。
如果復現,說明已有人發現 XSS 并成功注入了,但還沒大規模開始利用。程序猿們趕緊第一時間修 BUG 吧,讓黑客忙活一陣子后發現漏洞已經修復了:)
字符策略的缺陷
但是,光靠代碼字符串來判斷,還是會有疏漏的。尤其是黑客們知道有這么個玩意存在,會更加小心了。把代碼轉義用以躲避關鍵字,并將字符存儲在其他地方,以躲過長度檢測,即可完全繞過我們的監控了:
<img src="*" onerror="window['ev'+'al'](this.align)" align="alert('a mass of code...')">
因此,我們不僅需要分析關鍵字。在回調執行時,還需監控 eval、setTimeout('...') 等這類能解析代碼的函數被調用。
不過,通常不會注入太多的代碼,而是直接引入一個外部腳本,既簡單又靠譜,并且能實時修改攻擊內容:
<img src="*" onerror="$['get'+'Script'](...)">
在
Web 2.0 應用中,頁面裝載時間和瀏覽器渲染時間將成為決定性能的關鍵因素。我們在
測試過程中不僅需要手動觸發
性能測試工具,而且需要模仿不同的用戶行為,包括不同的瀏覽器、不同的網絡條件和不同的使用習慣。在這種情況下我們需要新的方法與工具來覆蓋特別是瀏覽器端的性能測試。用
Selenium WebDriver + Grid2 可以解決瀏覽器端性能測試中的自動化和并發性問題。本文介紹了這種新的測試框架和腳本的開發過程,并結合案例給出了具體的代碼和結果分析。使用 Selenium WebDriver+Grid2 可以將已有的
自動化測試資源為性能測試服務,將測試人員從繁雜的手動測試中解放出來,專注于結果分析和問題解決。
我們需要新的方法與工具來覆蓋瀏覽器端的性能測試
在瀏覽器端的性能測試中遇到的一些問題
在傳統的
互聯網應用中,由于瀏覽器端的時間消耗比較有限,并且頁面裝載時間也比較簡單且有限,所以在對傳統互聯網應用的響應時間分析中,我們通常忽略頁面裝載時間和瀏覽器渲染時間,而著重分析服務器響應時間。而在 Web 2.0 應用中,頁面裝載時間和瀏覽器渲染時間將成為決定性能的關鍵因素,所以我們需要新的方法與工具來覆蓋特別是客戶端的性能:瀏覽器響應時間。
我們使用一些工具來幫助我們測試瀏覽器端的性能;比如說 DummyNet 可以模擬不同網絡帶寬和延遲,以便我們分析不同網絡情況下的的網頁性能;HttpWatch 可以分別記錄每個請求的服務器端響應時間、頁面裝載時間、瀏覽器渲染時間;DynaTrace AJAX 是一種詳細的底層工具,它不僅可以顯示所有請求和文件在網絡中的傳輸時間,還會記錄瀏覽器 render、CPU 消耗、JavaScript 解析和運行的詳細情況。我們使用這些工具的時候大多是手動觸發,為了得到數據和分析報告,必須將用戶案例在不同的瀏覽器上手動實現,并且在某一時刻打開或關閉工具和保存結果。過多的手動操作使我們很難專注于數據分析和發現問題。所以我們需要解決以下問題:
需要有工具在模仿用戶使用 web 應用程序的同時自動觸發這些工具,生成分析數據。
可以同時模仿不同的用戶行為,包括不同的瀏覽器,網絡條件和使用習慣。
下面的章節將介紹如何利用自動化工具 Selenium 來搭建測試網頁瀏覽器端性能的測試框架,實現瀏覽器端自動測試和并行測試。
瀏覽器端性能測試框架中用到的技術
我們的測試框架主要采用了以下技術:
Selenium WebDriver
Selenium 是針對 Web 應用的測試框架,支持多種瀏覽器和多種編程語。WebDriver 通過原生瀏覽器支持或者瀏覽器擴展直接控制瀏覽器。它提供了一個更為簡單,一致的編程接口并且解決了在 Selenium-RC API 遇到的問題。Selenium WebDriver 更好的支持了動態網頁。它的目標是為現代的網頁程序測試提供設計良好的面向對象的 API。
Selenium Grid2
Selenium Grid 允許用戶將測試案例分布在幾臺機器上并行執行。用戶可以在一個集中控制點控制不同的環境。在不同的瀏覽器 / 系統組合上面更為容易的運行測試案例。允許用戶更多的利用虛擬資源減少了維護測試環境的成本。Selenium Grid2 很好的支持了 WebDriver。利用原有的 WebDriver 腳本和 Grid 技術就可以將不同的
測試用例分布在不同的機器或虛機上,在特定的瀏覽器版本上運行,自動地啟動關閉分析工具,統一地保存測試結果。Selenium Grid2 的機制如圖 1,啟動一個中央節點(Hub),然后啟動多個遠程控制節點(rc),啟動 rc 時告知 Hub 的位置,這樣這些 rc 就可以注冊到 Hub 上,測試程序與 Hub 通訊,當測試被并發地發給 Hub 時,Hub 會自動將這些測試命令分發給已經注冊的 rc,rc 接到命令后執行測試。
TestNG
本測試系統還使用 TestNG 工具來輔助配置自動測試。TestNG 是測試 Java 應用程序的框架之一。TestNG 以其靈活性和參數化成為定義 Selenium 的驅動驗收測試的首選。它通過一些語義注釋來傳遞測試的參數,定義測試腳本的順序并配置運行時的性能。用戶可以通過配置來生成各式測試報告,十分方便。
圖 1. Selenium Grid2 的機制

Ant
在框架中我們使用 Ant 來構建版本 , 然后部署到測試環境中去 , 再然后執行測試腳本到生成發送測試報告 .
瀏覽器端性能測試框架的實現
Selenium WebDriver 編寫測試腳本
利用 Selenium WebDriver 為動作執行和頁面跳轉進行設計。Selenium WebDriver 可以模擬用戶上網的行為,添加計時代碼用以測算用戶某個動作執行的時間或是頁面跳轉需要的時間。以用戶登錄為例,在輸入用戶名和密碼之后開始用 startTrans 函數開始計時,在點擊提交按鈕頁面跳轉之后用 endTrans 函數結束計時。
清單 1. 在 Selenium 中添加計時代碼

利用 DummyNet 模擬不同網絡條件下用戶的案例執行。帶寬模擬與控制是性能測試的一個常規需求。很多時候我們必須能夠提供可變的帶寬、上行和下行速率,從而獲得這些條件下性能的表現。開源工具 DummyNet 是一款優秀的網絡控制工具,它通過重載本機的網卡驅動,提供給開放人員命令行接口去模擬帶寬可變利用。調用 DummyNet 的命令后執行 Selenium 腳本可以設置網絡帶寬和延遲。以下命令限制從源地址到目的地址的流量帶寬 2Mb, 延遲 300ms。
ipfw add pipe 2 ip from %sourceip% to %targetip% out proto ip
ipfw pipe 2 config delay 300ms bw 2Mbit/s
利用 HttpWatch 和 DynaTrace AJAX 為特定的頁面請求記錄時間。HttpWatch 和 DynaTrace AJAX 是兩款強大的網頁數據分析工具。它們通常集成在瀏覽器的工具欄。以往我們通過手動打開這些工具在瀏覽頁面的同時收集所需的分析數據。在新的框架中 Selenium 腳本可以調用它們的接口驅動瀏覽器從會話中啟動分析工具并收集保存性能信息。
清單 2. Selenium 腳本調用 DynaTrace AJAX 接口
Selenium Grid2 集中控制測試環境
步驟一:啟動中央節點(Hub)。Hub 會接收測試請求并將它們分布在正確的節點上。啟動 Hub 的默認端口為 4444,也可以通過參數指定端口。打開地址:http://localhost:4444/grid/console,通過頁面可以觀察 Hub 的狀態。啟動命令如下 :
java -jar selenium-server-standalone-2.14.0.jar -role hub
步驟二:配置和啟動遠程控制節點(rc)。遠程節點是您需要測試的測試機所在的物理機或虛擬機。啟動節點時需要傳入 Hub 的 IP 地址和端口號。默認情況下,節點的端口為 5555,每個節點可以啟動 11 個瀏覽器,包括 5 個 firefox、5 個 chrome 和 1 個 ie 瀏覽器。最多有 5 個并行的測試案例。用戶也可以通過參數指定端口和瀏覽器,以下命令將會啟動 Linux 遠程節點上的三個 firefox 瀏覽器進行并行測試,它們指向在另一臺機器的 Hub 節點。
java -jar selenium-server-standalone-2.14.0.jar -role node \
-hub http://remotehost:4444/grid/register \
-browser browserName=firefox,version=3.6,maxInstances=3,\
platform=LINUX
Selenium WebDriver 和 Grid2 并發執行測試用例
TestNG 可以很方便的加載在已經開發好的 Selenium WebDriver 測試案例,測試用例中的參數通過 @Parameters 傳遞。在測試中我們需要將同一個測試案例發送到不同的節點或是瀏覽器上執行。所以我們從已有的 Selenium 腳本中選出案例后用 @Test 標注并加入節點的 IP、端口和瀏覽器類型參數。
清單 3. 將 TestNG 加載在已有的 Selenium 測試腳本
@Test
@Parameters({"seleniumHost", "seleniumPort", "bs"})
public void sitenav(String Host,String Port,String browser)
TestNG 要求將所有要運行的測試用例都記錄在一個叫 testng.xml 的文件中,然后根據該文件中的測試用例順序依次執行測試。通過 suit 標簽的屬性可以指定 method、tests 或是 classes 的并發執行。我們在 testng.xml 中將測試的并行粒度設為 Test, 用參數傳入不同的 IP 地址、端口號、瀏覽器名稱,形成不同的測試案例,以便在不同節點上的不同瀏覽器中并行執行。
清單 4. testng.xml 中有關并行測試案例的配置
最后用 Ant 從命令行運行 TestNG 類。用戶通過命令行啟動 Ant,讀取目標文件 build.xml 來獲得項目的參數,例如目標定義、類路徑、引入的文件等。Ant 通過讀取 TestNG 的配置文件 testng.xml 來獲取參數,將 Java 代碼編譯成 TestNG 類,并且根據配置文件中傳入的參數和順序來執行這些類。以下是在 Ant 的 build.xml 里面如何正確配置 TestNG。
清單 5. Build.xml 里配置 TestNG
應用示例
IBM Connections 3 是 IBM 專門為滿足企業業務需求而設計的一款社交軟件。我們用它作為目標程序來測試其瀏覽器端的性能。用 Selenium WebDriver 編寫腳本模擬用戶依次瀏覽主頁、登陸、個人資料、博客等主要頁面。我們將同樣的腳本在不同網絡環境、不同操作系統、不同瀏覽器中并行執行,并且記錄頁面的響應時間,完整下載時間,對不同瀏覽器,不同網絡條件下的頁面響應時間加以比較。測試過程如下:
選取幾臺測試機作為測試節點,測試機器上具有我們所需要的操作系統、瀏覽器類型、測試分析工具,選取一臺測試機作為 Hub。配置和啟動 Hub 和測試節點。
準備不同類型的測試案例 Test1.....TestN。根據操作系統、瀏覽器和所需的測試工具,在 testng.xml 中將測試腳本分配給相對應的測試節點 Node1.......NodeN 形成不同的測試案例。圖 2 是測試節點配置和測試案例的分布圖。
編輯 build.xml 文件。執行 ant 命令。通過網頁觀察 Hub 的狀態和測試節點的執行情況。
測試執行完,從各節點和 Hub 上收集測試數據加以整理和分析。
測試數據從幾個方面獲得。有關測試環境的數據將會直接從配置文件獲取,簡單的頁面響應時間會由 Selenium 腳本獲取。瀏覽器響應時間分解和網頁分析數據將會由 HTTPWACTH 等輔助測試工具獲得。圖 3 是由部分測試數據經過匯總整理后形成的表格。從表格中可以看到同一個頁面請求在不同配置的測試機上的響應時間,如果該機器加載了性能分析工具,還可以通過抓取 HTTPWACTH 分析結果進一步得到響應時間分解。
小結
利用 Selenium WebDriver 和 Grid2 可以將已有的自動化測試腳本為性能測試服務,并發、自動地執行測試案例節省了時間和人力,使測試人員能夠將精力集中在數據分析和性能調優上。靈活的配置測試環境,包括網絡環境、操作系統和瀏覽器類型,可以在短時間內得出不同情況下目標程序的客戶端性能數據,方便比較和發現問題。
在進行負載
測試(Load Test)是要監控服務器的CPU、內存、磁盤、網絡的情況。如何監控Ubuntu的情況呢。
1、安裝rstatd,sudo apt-get install rstatd,如果無法apt安裝,可以下載安裝。
2、啟動rpc.rstatd
查看是否正常啟動,用如下命令
rpcinfo -p [root@localhost ~]# rpcinfo -p program vers proto port 100000 2 tcp 111 portmapper 100000 2 udp 111 portmapper 100024 1 udp 676 status 100024 1 tcp 679 status 100001 3 udp 691 rstatd 100001 2 udp 691 rstatd 100001 1 udp 691 rstatd |
3、在
LoadRunner Controller的run界面中,添加System Resource Graphs下的Unix Resource,在Unix Resource圖上右鍵Add Measurements,然后點擊Add,填寫ip如192.168.1.99,默認只有三個指標,在下面的Add中可以添加其他指標。
4、下面說一下各種指標的情況
CPU指標
Average load
上一分鐘同時處于“就緒”狀態的平均進程數,< CPU個數 * 核心數 * 0.7
CPU utilization
CPU 的使用時間百分比,如果在75%以上,則可以考慮換CPU了
Swap-in rate
正在交換的進程數
Swap-out rate
正在交換的進程數
Context switches rate
每秒鐘在進程或線程之間的切換次數
System mode CPU utilization
在系統模式下使用 CPU 的時間百分比
User mode CPU utilization
在用戶模式下使用 CPU 的時間百分比
Interrupt rate
每秒內的設備中斷數
內存
Page-in rate
每秒鐘讀入到物理內存中的頁數
Page-out rate
每秒鐘寫入頁面文件和從物理內存中刪除的頁數
Paging rate
每秒鐘讀入物理內存或寫入頁面文件的頁數,如果持續在幾百,可能要加大內存了
LoadRunner采集的數據中,內存的使用情況是沒有的,可以裝sar,然后用sar來觀察:
可以使用該命令sar -n DEV -u -r 3 120 > perform.log
這個命令3秒采樣一次,共采樣120次
360秒=6分鐘,可以根據自己的需要調整 3 和 120 這兩個值。perform.log是保存的文件名
磁盤
Collision rate
每秒鐘在以太網上檢測到的沖突數
Disk rate
磁盤傳輸速率
網絡
Incoming packets error rate
接收以太網數據包時每秒鐘接收到的錯誤數
Incoming packets rate
每秒鐘傳入的以太網數據包數
Outgoing packets errors rate
發送以太網數據包時每秒鐘發送的錯誤數
Outgoing packets rate
每秒鐘傳出的以太網數據包數
pps是
以太網傳輸最小包長是64字節。包轉發線速的衡量標準是以單位時間內發送64byte的數據包(最小包)的個數作為計算基準的。
對于千兆以太網來說,計算方法如下:
1000Mbps/((64B+8B+12B)×8bit)=1.488095pps
說明:當以太網幀為64Byte時,需考慮8Byte的前導符和12Byte的幀間隙的固定開銷。
在以太網中,每個幀頭都要加上了8個字節的前導符,前導符的作用在于告訴監聽設備數據將要到來。然后,以太網中的每個幀之間都要有幀間隙,即每發完一個幀之后要等待一段時間再發另外一個幀,在以太網標準中規定最小是12個字節,然而幀間隙在實際應用中有可能會比12個字節要大,在這里我用了最小值。每個幀都要有20個字節的固定開銷。(另外這20字節的信息是不能通過抓包軟件抓下來的)
因此一個全雙工線速的千兆以太網端口在轉發64Byte包時的包轉發率為1.488Mpps。
以下是常用以太網端口的包轉發率:
1、萬兆以太網:14.88Mpps
2、千兆以太網:1.488Mpps
3、百兆以太網:0.1488Mpps
4、十兆以太網:0.01488Mpps(14.88Kpps)
宇宙中心接任務
上午10:05
五道口漫咖啡,Jarod擺弄著新淘來的Nexus5手機,時而飲一口桌上的焦糖拿鐵,間或偷眼瞄一下鄰桌的長腿妹子。
上午10:30
Allison如約而至,開始為Jarod介紹本次任務,并演示本次任務的目標:對
Android應用“探路者”進行性能評估。“探路者”是Allison所在公司的新研發的產品,既有給用戶展示的動態界面,也會常駐
手機后臺。其核心業務模式是將位置信息上報云端,結合云端數據進行復雜的計算后,將計算結果返回給手機。“除了CPU的消耗,還有流量的消耗和GPS模塊的使用”,Jarod默默記下。
由于“探路者”是Allison本次創業的關鍵,不容有失,但創業公司資源緊張,團隊中并沒有專門的終端
測試專家,產品臨近發布,心里不免惴惴,于是便有了這次與Jarod的合作。
Jarod現在是一名自由職業者,曾任職于世界知名IT企業的安全產品部門,有著豐富的軟硬件測試經驗和黑客經驗,如今五子登科,便早早主動退休,過起了養魚遛鳥的
生活。閑來無事,Jarod也會偶爾從熟人圈子接些私活,與人方便,也順便瞞著夫人賺些零花錢,以維護經濟自由。
上午11:15
Allison交接了委托任務后先行離去,Jarod先簡單瀏覽了“探路者”的界面,把玩一番后,又開啟了核心的“隨行”功能,“隨行”功能是持續型功能,要在運動中才能看出效果,不過靜止的狀態也是要保證正常處理的。雖然Allison說過“探路者”已經通過了
功能測試和用戶體驗,基本功能是沒問題的,但Jarod作為一個資深的測試者,懷疑一切是職業的第一信條,質量如何,一定要親身試過才知道。
上午11:30
Jarod判定“探路者”的基本功能正常,于是對“探路者”進行啟動狀態的基本性能評估。Jarod先將“探路者”退出,然后從測試工具GT中啟動“探路者”,啟動完成后回到GT界面查看,“探路者”的CPU占用在0%-2%波動,結合手機默認的調頻策略判斷比較正常,jiffes(CPU時間片)值累積在400多,內存PSS值穩定在36M,上行流量和下行流量都不到1K,無異常,數據存檔。再啟動電流監控觀察,1分鐘后平均電流是220mA,從數據變化曲線上看沒有連續的峰值和規律性波動,待后面長時間測試時綜合多項指標再分析吧。回到“探路者”界面,從GT的懸浮框中觀察,CPU占用在0%-5%波動,PSS值和上下行流量也沒有異常的增長,OK。
一人一機一GT
上午11:50
Jarod完成“探路者”的啟動性能驗證,把采集的數據存檔備案后,開始場測(到產品的實際使用環境中測試)前的準備:清理了“探路者”的緩存,給手機充滿電并固化了亮度等設置,規劃好場測路線,順便吃午飯。40分鐘后,Jarod喝完最后一杯檸檬水,只帶上Nexus5,便奔香山去了。作為獨行的極客,Jarod追求極致簡約:MBA,mini機箱,無線鍵鼠,網線自然是見不得的,所以當Jarod遇到“GT隨身調”時,一種幸福感無可言狀,因為GT可以獨立完成手機的大部分性能指標的采集,以后場測時他不必再背著筆記本,不必再帶著電流計了。Jarod出門前計算了一下,本次任務的實測和數據采集,1人1機1GT足矣,充電寶也不帶了。
下午12:45
Jarod上了輛直達香山的311路,找了個座位坐下,先打開了GT的“月光寶盒”,再啟動“探路者”并切換到 “隨行”模式,接著使用GT對其進行性能監控,然后在GT的懸浮框中對CPU、PSS、流量值即時觀察。行進中,PSS逐漸躍升到90M穩定下來,觀察一會沒有明顯的內存泄露跡象,OK。但是“探路者”的CPU占用持續在25%~40%,這對主流的4核手機已經算是高消耗了。Jarod等了3分鐘看到CPU沒有下降的趨勢,于是進入GT界面,發現這幾分鐘的平均電流達到660mA。Jarod心里一緊,對Nexus5的2100mAh的電池來說,持續運行在“隨行”模式下的手機只能支撐3個小時,與“隨行”的目標續航5小時有不小差距。
公交上忙碌的Jarod
Jarod立即觀察流量,10分鐘的下行流量有110K,在沒抓包的情況看不出明顯的疑點。Jarod思考了一下,對著“隨行”界面上幾個按鈕試了試,其中有個點下去,屏幕上彈出“停止實時路況”的提示,再看平均電流,這回降到500mA。“先反饋上去,看看這塊邏輯能有多大的優化空間吧”。Jarod想。“即使開啟實時路況的耗電優化到550mA,續航時間能達到4小時,離目標還差1小時呢”。Jarod又開始盯著手機觀察“嗯,畫面的動畫倒是挺順滑的……咦?好像忽略了什么……會不會過于順滑了?”這個想法閃念而出,Jarod立即切到GT的界面,激活FPS(幀率)的監測,回到“隨行”界面觀察。從GT的懸浮框中,Jarod看到FPS的數字變換著:59,60,60,58,60,59,59,59……“果然如此,Allison他們并沒有給動畫做幀率上的限制,所以Android系統會盡可能的刷新UI畫面,達到上限60幀”。其實,從Jarod的經驗看,“隨行”功能并不需要這么高的幀率,即使把動畫刷新降低到30幀,普通用戶也不會感覺到有卡頓的。而降低幀率,最直接的好處就是減少耗電。
Jarod立即記下:“建議1:在不影響動畫效果下降幀率。建議2:優化‘實時路況’的算法性能。”
Jarod又持續的對“隨行”的性能數據進行了10分鐘的采集,確認沒有新的發現后,即保存了數據,進行第二個核心功能“潛行”的測試。“潛行”是“探路者”退到后臺或手機滅屏后,“探路者”仍然以語音提示的方式對用戶提供服務的模式。
下午13:25
Jarod開始“潛行”模式的測試,還是那些監測指標。順利的“潛行”了10分鐘后,Jarod看到沒有明顯異常,只是產生了140K的下行流量,比“隨行”模式還要多一些,而上行流量也達到70K,。雖然流量一般會根據環境的復雜變化出現較大波動,不過Jarod謹慎起見,還是決定上山測試“潛行”的時候,用GT抓個包回去分析一下。
登頂香爐峰
下午13:55
Jarod到達香山,啟動抓包后,開始測試爬山中的“潛行”。五月的香山風景迤邐,涼風習習,Jarod工作兼怡情,暢快之余,不免健步如飛,四刻鐘后即抵達香爐峰。掏出手機,點亮屏幕一看,頓時傻眼,原來“探路者”不知何時已棄主crash于寶剎云間去了。“剛剛爬上香爐峰的時候,‘探路者’還語音提醒了啊……”,回想到這里,Jarod心中有所明悟。熟練的保存了抓包文件后,Jarod切換到GT的Logcat查看界面,立即把手機緩存的Logcat日志保存下來,然后開始回溯。所幸,在3分鐘前的位置發現了“探路者”crash的蹤跡。“有跡可循的crash都不會太難解決,又多了筆收獲”,Jarod心中盤算。
下午15:10
Jarod看到Nexus5的電量已不足30%了,便關閉了早先在GT上打開的“月光寶盒”準備下山。其實“月光寶盒”是一個記錄用戶移動軌跡的功能,利用它,Jarod完整的記錄了這次測試過程中的位置變化,后續若有回歸測試,在Android手機模擬GPS的模式下回放錄制好的移動軌跡,就不必再跑一趟香山了。
下午16:15
Jarod趕在大塞車之前回到家中,連上電腦分析抓包后,連同一天的測試數據一起整理了報告給Allison,Allison立即回復并預約了3天后進行回歸測試,Jarod表示接受挑戰。
下午16:30
算算時間孩子該放學,妻子也該串門回來,Jarod搖身一變回復了本職,開始準備一家3口的晚餐。
【文件上傳漏洞】
1.對用戶上傳的文件沒有進行充分合理的驗證,導致被上傳木馬等惡意文件并執行
驗證方式可能有:客戶端JS檢測、服務器MIME類型檢測、服務器目錄路徑檢測、服務器文件擴展名檢測、服務器文件內容檢測。
常見的方式:服務器充分驗證文件類型及文件內容、服務器端重命名
其他限制:服務器端上傳目錄設置不可執行權限
【CSS漏洞】
http:/host/xss/example1.php?name = song <script>alert("hello")</script>
測試的時候也可以直接簡單點,輸入簡單的這些實體字符(例如:<>()"),然后查看前臺的源代碼是否做轉義,如果沒有那么自己可以輸入任何的腳本都是有可能執行的。
Ps: CSS 繞過的方法,其實是很多的,一般我們可以根據后臺的返回來通過不同的方法繞過進行測試
相當大一部分程序員在編寫代碼的時候,沒有對用戶輸入數據的合法性進行判斷,使應用程序存在安全隱患。用戶可以提交一段
數據庫查詢代碼,根據程序返回的結果,獲得某些他想得知的數據,這就是所謂的SQL Injection,即SQL注入。
SQL注入:
還有所謂的命令注入:
為什么使用NoSQL數據庫?
1、阻抗失衡
關系模型和內存中的數據結構不匹配
采用更為方便的數據交互方式提升開發效率
2、待處理的數據量很大
數據量超過關系型數據庫的承載能力
大集群的出現
在成本方面,集群中應用關系數據庫,許可費用是一筆很大的支出;
橫向擴展和縱向擴展:關系數據庫一般只能是縱向擴展,通過對單機服務器的性能換代增強而實現;而對于擴展到多個服務器,
DBMS先天不足;(DBMS不是設計給集群使用的)
3、對數據的訪問效率要求高
NoSQL數據庫的分類
鍵值數據庫
1、產品
Redis
BerkerleyDB
Memcached
Project Voldemort
Riak
LevelDB
2、適用場景
存放會話信息
用戶配置信息
購物車數據
3、不適合的場景
數據間有大量關系
含有多項操作的事務
根據鍵值的部分來查詢數據
操作關鍵字集合
文檔數據庫
1、產品
MongoDB
CouchDB
RavenDB
Terrastore
OrientDB
2、適用場景
事件記錄
內容管理系統及博客平臺
網站分析及實時分析
電子商務應用程序
(需要較靈活的模式,低成本建立數據模型)
3、不適合場景
包含多項操作的復雜查詢
查詢持續變化的聚合結構
列族數據庫
1、產品
HBase
Amazon SimpleDB
Cassdndra
Hypertable
BigTable(google)
2、適用場景
事件記錄
(保存應用程序狀態,運行中遇到的錯誤)
CMS及博客平臺
計數器
3、不適用場景
需要ACID事務
查詢模式變化頻繁的場合
圖數據庫
1、產品
FlockDB
HyperGraphDB
Infinite Graph
Neo4J
OrientDB
2、適用場景
互聯數據
推薦引擎
基于位置的服務
3、不適用場景
更新全部或某個子集的實體
附思維導圖
Java 數據對象在沒有持久存儲到業務表時,可能需要臨時存儲。原因如下:有些持續時間長的任務需要數據對象,但是該任務沒完成時數據對象不能持久存儲;當服務在持久任務執行期間重啟后恢復任務時需要數據對象。這樣的情況下,數據對象的臨時存儲是有意義的。
java對象存儲到Blob
ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream oo; try { oo = new ObjectOutputStream(bo); oo.writeObject(libraryEtextJn); } catch (IOException e) { e.printStackTrace(); } Blob blob= Hibernate.createBlob(bo.toByteArray()); |
從Blob讀取Java對象
ObjectInputStream is = new ObjectInputStream(new BufferedInputStream(blob.getBinaryStream()));
LibraryEtext libraryEtext = (LibraryEtext)is.readObject();
Hibernate配置Blob字段
<property name="liberary" column="LIBERARY" type="blob" />
判斷當前
Windows系統是win2000,winXP,winVista,win2003,win7...
private static IntPtr GetSysTrayWnd() { IntPtr handle = FindWindow("Shell_TrayWnd", null); handle = FindWindowEx(handle, IntPtr.Zero, "TrayNotifyWnd", null); if (IsWindowsXP || IsWindows2000 || IsWindows2003 || IsWindowsVista || IsWindows7) { if (IsWindows2000) { handle = FindWindowEx(handle, IntPtr.Zero, "ToolbarWindow32", null); return handle; } else { handle = FindWindowEx(handle, IntPtr.Zero, "SysPager", null); handle = FindWindowEx(handle, IntPtr.Zero, "ToolbarWindow32", null); return handle; } } else { return handle; } } |
判斷當前
Windows系統是win2000,winXP,winVista,win2003,win7...
private static IntPtr GetSysTrayWnd() { IntPtr handle = FindWindow("Shell_TrayWnd", null); handle = FindWindowEx(handle, IntPtr.Zero, "TrayNotifyWnd", null); if (IsWindowsXP || IsWindows2000 || IsWindows2003 || IsWindowsVista || IsWindows7) { if (IsWindows2000) { handle = FindWindowEx(handle, IntPtr.Zero, "ToolbarWindow32", null); return handle; } else { handle = FindWindowEx(handle, IntPtr.Zero, "SysPager", null); handle = FindWindowEx(handle, IntPtr.Zero, "ToolbarWindow32", null); return handle; } } else { return handle; } } |
在編寫
Java代碼中,我們或多或少會遇到下面的一些案例,對于這些情況,我們怎么做?那么接下來我介紹一些方法,大家可以看一下:
1.判斷數組{"a","b","c","d"}是否包含有"a",見以下代碼,你喜歡用哪一種呢,我推薦使用ifcontainsByArrays,因為該方法內部更嚴謹一些。
public static void main(String[] args) { String [] strs = {"a","b","c","d"}; Test t = new Test(); System.out.println(t.ifcontains(strs, "a")); System.out.println(t.ifcontainsByArrays(strs, "a")); } public boolean ifcontains(String [] strs, String str) { for (String s : strs) { if (s.equals(str)) { return true; } } return false; } public boolean ifcontainsByArrays(String [] strs, String str) { // 注意:Arrays.asList()方法返回的是 java.util.Arrays.ArrayList而不是java.util.ArrayList return Arrays.asList(strs).contains(str); } |
2.在循環中刪除一個列表中的元素,見如下代碼,我建議使用removeByIterator方法,因為不需要再創建新的list。
public static void main(String[] args) { List<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d")); List<String> list1 = new ArrayList<String>(Arrays.asList("a", "b", "c", "d")); Test1 t = new Test1(); for (String s : t.removeByIterator(list, "c")) { System.out.print(s); } System.out.println(); System.out.println("----------------------------"); for (String s : t.removeByNewList(list1, "c")) { System.out.print(s); } } public List<String> removeByNewList(List<String> list, String str) { List<String> newList = new ArrayList<String>(); for (String s : list) { if (!s.equals(str)) { newList.add(s); } } return newList; } public List<String> removeByIterator(List<String> list, String str) { Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String s = iterator.next(); if (s.equals(str)) { // 移除迭代器返回的元素 iterator.remove(); } } return list; } |
暫時先列舉兩個,有時間的話,繼續
背景:
因剛開始安裝
mysql的時候沒有考慮后期磁盤分區空間問題,所以mysql的數據保存目錄就按安裝默認的目錄/usr/local/mysql/(我下載的是一個lamp一鍵安裝包安裝的mysql),現在隨著服務的運行發現/usr/local/mysql所在分區空間使用快接近100%了,怎么辦?不想重裝mysql,我服務器大部分空間mount在/app下了,于想想到了linux下的ln -s命令
OK 下面開操作了(注:以下操作均在零晨1點操作,記住先將數據備份成sql文件):
1. service mysqld stop
2. 在/app 下新建一個目錄mysqldir : mkdir mysqldir #在新分區上建立mysqldir目錄
3. cd /usr/local/mysql && cp -r /app/mysqldir 新數據拷到新目錄mysqldir下
4. rm -rf mysql //把原來的數據目錄刪掉
5 ln -s /app/mysqldir mysql
6. service mysqld start
本以為沒啥問題,運行第6條命令后發現mysql起不來,所如下錯誤:
Starting MySQL. ERROR! The
server quit without updating PID file (/usr/local/mysql/data/AY13121220352352963dZ.pid).
運行service mysqld status輸出如下提示信息:
ERROR! MySQL is not running, but lock file (/var/lock/subsys/mysql) exists
ok 這個問題應該很常見了,我的解決辦法是運行如下命令:
1. chown -R mysql:mysql /app/mysqldir #設置新目錄權限歸mysql用戶所有
2. rm -f /var/lock/subsys/mysql
再執行service mysqld start 大功告成