1. 程式人生 > 其它 >獲取網頁引數

獲取網頁引數

實驗步驟一

百度大家應該都用過,我們可以在百度查詢一些想要了解的資訊。但是我們需要告訴它,我們想要查詢什麼內容,這就需要我們告訴伺服器我們需要查詢與什麼相關的內容,所以我們需要在搜尋框輸入我們想要查詢的資訊的關鍵字,這裡輸入的關鍵字就是給百度的伺服器傳入引數(以後簡稱傳參),百度的伺服器在接收了這個引數後,進行了一些處理,然後返回相關資料,比如我們輸入“網安實驗室”,百度就會給我們返回與“網安實驗室”相關度較高的網頁,如圖:

可以看到,輸入的關鍵字到了URL裡面,像這種給伺服器傳參的方法,就是HTTP協議中的GET方法,在GET方法中,查詢字串(名稱/值)是在URL中傳送的。其格式如下:

/demo.php?name1=value1&name2=value2

一般來說,只用來向伺服器獲取資訊,不向伺服器傳敏感資料。

要證明這是GET方法也很簡單,可以在網頁的任何位置滑鼠右擊,然後選擇檢視元素。

然後點選網路。

再點選重新載入,瀏覽器會重新載入該頁面。

此時會看到很多請求,每個請求都顯示了狀態碼(即狀態一欄)、請求方法、請求檔案、請求域名、請求原因、請求型別等。

在這些請求中,找到請求原因是document,請求型別是html的一行,這行就是搜尋所發起的請求,後面的一些請求都是在請求完成後發起的,因為第一次請求完以後,有些如圖片瀏覽器需要重新拉取。

在方法這一欄,可以看到都是GET,而且沒有POST方法,所以我們可以推斷我們給伺服器傳參是通過GET的方式傳過去的。

在狀態這一欄,有的可以看到狀態有的是200,有的是304,這是HTTP協議中的狀態碼。

狀態程式碼有三位數字組成,第一個數字定義了響應的類別,且有五種可能取值:

1xx:指示資訊--表示請求已接收,繼續處理

2xx:成功--表示請求已被成功接收、理解、接受

3xx:重定向--要完成請求必須進行更進一步的操作

4xx:客戶端錯誤--請求有語法錯誤或請求無法實現

5xx:伺服器端錯誤--伺服器未能實現合法的請求

常見狀態程式碼、狀態描述、說明:

200 OK//客戶端請求成功

400 Bad Request//客戶端請求有語法錯誤,不能被伺服器所理解

401 Unauthorized//請求未經授權,這個狀態程式碼必須和WWW-Authenticate報頭域一起使用

403 Forbidden//伺服器收到請求,但是拒絕提供服務

404 Not Found//請求資源不存在,eg:輸入了錯誤的URL

500 Internal Server Error//伺服器發生不可預期的錯誤

503 Server Unavailable//伺服器當前不能處理客戶端的請求,一段時間後可能恢復正常

在上圖中還可以看到304,304表示自從上次請求後,請求的網頁未修改過。伺服器返回此響應時,不會返回網頁內容。

如果網頁自請求者上次請求後再也沒有更改過應將伺服器配置為返回此響應(稱為 If-Modified-Since HTTP 標頭)。伺服器可以告訴瀏覽器自從上次訪問後網頁沒有變更,進而節省頻寬和開銷,同時提高使用者訪問速度,因為對於瀏覽器來說,它需要把所有的資源下載到本地渲染後展示出來。

點選上面說到的那個請求,一般會顯示在第一行,如果你做了其他操作導致有很多請求,可以先點選左上角的垃圾桶的圖示,清空所有請求,再點選一次重新載入。然後在右邊會顯示

請求的詳細內容,點選引數

在這可以看到我們給百度伺服器傳的引數,可以明顯看到我們給百度伺服器傳的引數不止一個,在查詢字串列表中,在冒號(:)左邊的是引數名,後面的是代表引數的值,在服務端,可以通過引數名來接收引數值,比如這裡引數rsv_bp的值是1,所以在服務端,通過rsv_bp這個引數名,就能接收到1這個值,然後服務端再根據這個值做相應的處理,如果還有不懂的也沒關係,後面會詳細介紹。

然後點選訊息頭,點選編輯和重發。

然後就可以看到原始頭了。

可以看到,wd引數的值已經不是直接顯示中文字元,而是顯示%後面跟上一個16進位制的數字,為什麼會這樣呢?其實這是中文字元URL編碼後的值。

雖然這裡顯示的是我們輸入的字元,但是在傳送給伺服器的時候,瀏覽器會自動給中文字元URL編碼。為什麼會這樣呢? 因為網路標準RFC 1738做了硬性規定:“只有字母和數字[0-9a-zA-Z]、一些特殊符號"$-_.+!*'(),"[不包括雙引號]、以及某些保留字,才可以不經過編碼直接用於URL。”。

一般來說,當我們向伺服器請求資源的時候,都是用GET方法,不應該使用GET請求來發送一些敏感資料,如:賬號密碼,因為如果賬號密碼也被包含在url裡面被髮送的,很容易被人看見,而且,GET請求中的引數會被記錄在web日誌中,如果有人通過惡意請求,可以讀web日誌,那他將可以看見所有使用者的賬號密碼。

我們來看下php是如何獲取使用者通過瀏覽器傳過去的引數的。

在PHP裡面,有許多預定義的變數都是“超全域性的”,這代表他們在一個指令碼的內部的任何地方都可以使用,即使實在函式或者方法中,要訪問這些“超全域性變數”也無需使用global $var;這些超全域性變數是:

$GLOBALS

$_SERVER

$_GET

$_POST

$_FILES

$_COOKIE

$_SESSION

$_REQUEST

$_ENV

本步驟中先了解$_GET。

$_GET這個超全域性變數是一個數組,它儲存了通過 URL 引數傳遞給當前指令碼的引數的值,也就是說,所有通過get方法傳過來的引數,都可以通過這個超全域性變數來取得引數對應的值,要取得指定引數的值,可以通過引數名來索引$_GET這個陣列。

在實驗環境中,已經配置了sqli.com域名,現在通過程式碼來看下如何獲取使用者傳過來的引數。在實驗環境中開啟火狐瀏覽器,訪問sqli.com/get.php?id=12&name=admin。以後涉及到需要訪問sqli.com的步驟,都在試驗機中完成。

這裡的?表示從?後面開始是傳引數,當我們需要同時向伺服器傳多個引數的時候,需要使用&來連線多個引數。這裡代表往伺服器傳了id、name這2個引數,值分別是12、admin。看程式碼更能直觀地理解含義。該頁面原始碼如下:

它的作用僅僅只是輸出使用者通過get方法傳過來的id和name這2個引數的值。

可以看到頁面輸出的就是我們傳過去的值,看別人的漏洞分析文章的時候,可能會經常看到說某某變數可控,在這裡,我們通過GET方法傳過去的值就是可控的,可控指的是:我們可以控制某個變數的值。

通過GET方法我們可以給伺服器傳多個值,比如還可以在後面加一些引數。現在我們給該頁面傳一些其他引數,然後列印下這個全域性變數。

首先進入C:\wamp\www\sqli目錄,開啟get.php,把前面輸出的程式碼註釋掉(在英文輸入狀態下,在每行的最前面輸入2個 / 表示註釋掉這一行),把最後一行取消註釋。改完以後的程式碼如圖:

var_dump用來列印變數的相關資訊,包括表示式的型別與值。

然後訪問sqli.com/get.php?id=12&name=admin&user=test&home=dd,訪問後輸出如下圖:

可以看到,該變數的型別是array,也就是陣列。在“=>”左邊的是這個陣列的鍵,右邊的是值。

需要注意的是,我們傳過去的引數,伺服器會原封不動地接受引數,如圖:

如果我們傳過去的值裡面有一些特殊字元,比如單引號、雙引號等服務端沒有任何處理,直接拼接sql語句的話,則會導致SQL注入、XSS等安全漏洞。想深入瞭解的可以學習“SQL注入原理與實踐”、“XSS跨站指令碼攻擊原理與實踐”等實驗。

實驗步驟二

除了上面講到的用GET方法給伺服器傳參,還有一種常用的傳參方法,那就是POST。如果通過POST方法傳參,那麼引數是在http請求的請求體中,並且是經過URL encode編碼的。

在登入的地方一般都是用POST方法,前面已經說過,如果包含敏感資訊,就應該用POST方法。

還是開啟百度,點選右上角的登入,彈出登入視窗:

在輸入框中隨便輸入一些內容,如果需要驗證碼,需要輸入正確的驗證碼。

然後點選登入。

提示輸入的賬號或密碼有誤,但是url並沒有改變,也就是說並沒有通過GET方法給伺服器傳引數,那麼伺服器是如何知道我們輸入的賬號和密碼的呢?

這裡雖然沒有通過get方法傳參,但是它通過post方法給伺服器傳參了。按F12或者滑鼠右擊頁面選擇檢視元素進入開發人員工具,進入網路面板。再次輸入賬號密碼以及驗證碼,在點選登入之前點選左上方的垃圾桶圖示清空內容

點選登入。

可以看到,第一個請求就是post請求。點選post請求的這一行。會彈出這個請求的詳情。

點選編輯和重發。

在請求主體中可以看到我們通過post方法提交的引數。你會發現裡面多了很多引數。在這裡是經過url編碼後的,看起來不方便,在引數這一欄看起來更方便。

可以看到給伺服器傳了很多引數,而我們輸入的只有2個,加上驗證碼也就三個引數,這說明百度自動帶入了一些其他引數,在裡面可以看到username、password這2個引數,從引數名我們就可以猜出分別代表我們輸入的使用者名稱和密碼,但是輸入的密碼明明只有幾個字元,為什麼到這裡就變成了一串很長的毫無規律的字串呢? 這說明我們輸入的密碼是被加密後才被髮送百度伺服器。

那麼PHP又是如何接受通過POST方法傳過去的引數的呢?

還記得前面介紹的PHP中的超全域性變數嗎?其中就有一個$_POST的超全域性變數,它也是一個數組,它儲存了使用者通過POST方法傳過去的引數。

關鍵程式碼已經寫了註釋,頁面很簡單,獲取使用者的輸入內容然後原樣輸出到頁面,當然在實際開發中,不是這麼簡單,一般都是獲取使用者的輸入後,儲存到資料庫中,當然在儲存之前還要先判斷該使用者是否已經註冊,如果資料庫中已經存在一個同樣的使用者名稱,則會提醒使用者該使用者已註冊。

那麼,又是如何得知陣列的哪個鍵對應使用者輸入的輸入框呢? 仔細檢視上面的html的程式碼。

每個輸入框都有個name屬性,我們可以通過這個name屬性的值來索引使用者的輸入框內容,比如,在使用者名稱的輸入框輸入:test,那麼可以通過$_POST['username'] 來獲取該輸入框的值,也就是說,$_POST['username']的值為test。

可能細心的朋友已經看出來了,這明明是一個php頁面,怎麼也可以寫html程式碼呢? 因為在php檔案中,php 直譯器只有在遇到 <?php 的時候,才開始把後面的程式碼當成php程式碼處理,不在<?php ?>內的程式碼不處理,直接原樣輸出。<?php 是php的開始標記,告訴php直譯器,在遇到結束標記前,都是php的程式碼。?> 是 php的結束標記。

瀏覽器開啟訪問http://sqli.com/post.php.

為了更方便地瞭解post傳參的位置,我們使用抓包工具來抓包,抓包工具通過修改瀏覽器代理的方法,來攔截瀏覽器傳送的資料包,攔截資料包以後,可以進行修改資料包,丟棄資料包等操作。

機器已經安裝好fiddler,點選桌面fiddler圖示開啟fiddler。

然後進入瀏覽器設定。

進入瀏覽器代理設定頁面。

選擇使用系統代理設定,點選確認。

重新訪問http://sqli.com/post.php,此時可以在fiddler看到我們剛才的請求,由於我們沒有設定攔截請求,所以預設直接允許請求了。

還有其他不相關的一些請求,可以不用管。點選這個請求,檢視這個請求的原始請求。

如果你是重新輸入的url訪問,或者之前沒有點選提交,那麼你可以看到這個請求是get請求而不是post,其中紅框標記中的內容為http請求頭。

其中第一行的GET說明這次請求是GET請求,後面的url表示本次請求的路徑,最後面的HTTP/1.1說明HTTP 1.1版本。

由於我們沒有在頁面的輸入框中填內容,所以該請求是GET請求,如果在輸入框中填了內容,那麼請求方式就會變成POST。

內容隨便,填好以後先不點提交,在fiddler中,發現有多餘的請求

先把這些請求清空掉。點選上方的X,然後選擇remove_all。

就會清空所有記錄,然後再點選提交。

可以看到,頁面輸入了我們輸入的內容,同時fiddler中,也顯示了我們本次請求,點選這個請求,檢視該請求詳情,同樣來到raw的介面檢視原始的http請求。

可以看到,請求方法變成了POST,此時,在下方,比上次GET請求明顯多了一些資料。

這些資料就是我們通過POST方法傳給伺服器的引數,這些資料所在的位置就是HTTP請求體中,HTTP請求頭和HTTP請求體用空行分割。

同樣的,POST請求的資料,也需要經過url編碼,可以在頁面輸入中文測試,填寫如下內容:

點選提交後,來到fiddler檢視這次請求的POST引數。

很明顯的經過了URL編碼,伺服器在接受這些引數值的時候,會自動URL解碼。所以輸出的字元還是我們在輸入框中輸入的內容。

同樣,如果服務端在接受使用者輸入的引數後,沒有進行處理,就直接拼接SQL語句進行查詢就可能會引起安全漏洞,比如最常見的SQL注入和XSS。原理可以參考“SQL注入原理與實踐”、“XSS跨站指令碼攻擊原理與實踐”等實驗。

通過前面的實驗,可以知道,GET方法和POST方法的區別,主要就是傳參的地方不同,GET方法通過URL傳參,POST方法在請求體中傳參。在實際應用中,GET方法多用來向伺服器發起請求獲取資源,而POST方法多用在需要修改資源的地方,以及一些敏感資訊使用POST方法,並沒有什麼規範強制要求在登入處使用POST方法。但是我們應該使用POST方法而不用GET方法,因為POST方法相對來說,比GET方法私密,因為引數不會顯示URL而在請求體中,使用者看不到請求體。有些請求必須使用POST方法。比如向伺服器傳檔案等。因為伺服器明確規定要求使用POST方法。