2010年12月7日 星期二

ASP.Net 取得使用者真實 IP

ASP.Net 如何抓取 User 的 IP ? 於 Request.UserHostAddress 屬性,可以看到微軟的解釋為:取得遠端用戶端的 IP 主機位址。這句話沒錯,只是現在許多使用者的電腦與網站主機之間,常常會透過 Proxy 伺服器來存取主機上的網頁,也許是你自己設定的,也許是公司網管設定的,或是 ISP 業者,都有可能讓 Request.UserHostAddress 抓不到使用者的真實 IP。


更進一步來說,可能會聽到資安專家,直接要你死了這條心,在網路世界裡,是無法找到真實 IP的,因為網路封包的標頭都是可以假冒的,也就是說,你抓到的 IP ,有可能是假的。有許多論壇的網站,都很習慣將發表文章的作者 IP 連同文章內容一起公開,也就是說,該網站的運作方式,會去抓 User 的 IP ,然後存進資料庫。這動作,曾經讓不安好心的人起了個念頭。假設這個站台不注重資安,是用了

string sql = "INSERT INTO (IP) VALUE ('" + IP + "')";

這寫法, 會讓有心人士將 HTTP 標頭裡將原先紀錄 IP 位址的地方改成惡意的SQL 指令碼,那當你的系統在執行儲存 IP 動作時,也同時執行了惡意程式碼。而這一點,是所有準備要留下 IP 紀錄的工程師,必須要考慮的一個安全議題。當然,養成好習慣,驗證你所要傳給 SQL 的參數內容,是自保的不二法門。

以下提供網友提供的驗證是否為合法 IP 的 Regular :

^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$

用 C# 寫成 CheckIP( )

///
/// 檢查 IP 是否合法
/// 
/// strPattern:需檢測的 IP
/// true:合法 false:不合法
private bool CheckIP(string strPattern)
{
// regular: ^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$
Regex regex = new Regex("^\\d{1,3}[\\.]\\d{1,3}[\\.]\\d{1,3}[\\.]\\d{1,3}$");
Match m = regex.Match(strPattern);

return m.Success;
}

撇開特別的情況,抓真實 IP 的功能,還是得寫,總不能讓你無法跟老闆交待吧!但記得要先跟他打預防針,讓他理解到,無法保證百分百取得真實 IP,因為 IP 有可能被假冒,但正常情況下,取得大多數的使用者 IP 則是可行的。

假設使用者沒有透過代理伺服器,則使用 Request.ServerVariables["REMOTE_ADDR"] 就可以抓到 IP;但如果有透過代理伺服器,則要改成使用 Request.ServerVariables["HTTP_X_FORWARDED_FOR"]。而在kingwkb的专栏 的一篇文章「[hidotnet]真正的取真实IP地址及利弊」提到,如果使用者的環境是使用多重代理伺服器時,則使用Request.ServerVariables["HTTP_X_FORWARDED_FOR"] 會得到「真實IP,第一層代理IP,第二層代理IP,...」(ex:「140.134.4.4,140.134.4.250,140.128.2.10」)的結果。而 kingwkb 文章也進一步去將所謂內部 IP 的資訊事先在程式中進行了篩選,只留下相對可信的資訊。


最後整理了測試程式,結果如下:

撰寫了 GetIP( ) 函式來取得真實 IP,並搭配 CheckIP( ) 來驗證所抓的 IP 是否合法。

protected void Page_Load(object sender, EventArgs e)
{      
    //自己定義的 Label,用來顯示 IP 訊息
    lbIP.Text = GetIP();
       
}

private string GetIP()
{
    string ip;
    string trueIP=string.Empty;

    //先取得是否有經過代理伺服器
    ip=Request.ServerVariables["HTTP_X_FORWARDED_FOR"];

    if (!string.IsNullOrEmpty(ip))
    {
        //將取得的 IP 字串存入陣列
        string[] ipRange = ip.Split(',');

        //比對陣列中的每個 IP
        for (int i = 0; i < ipRange.Length; i++)
        {
            //剔除內部 IP 及不合法的 IP 後,取出第一個合法 IP
            if (ipRange[i].Trim().Substring(0, 3) != "10." &&
                ipRange[i].Trim().Substring(0, 7) != "192.168" &&
                ipRange[i].Trim().Substring(0, 7) != "172.16." && 
                CheckIP(ipRange[i].Trim()))
            {
                trueIP = ipRange[i].Trim();
                break;
            }
        }
            
    }
    else
    {
        //沒經過代理伺服器,直接使用 ServerVariables["REMOTE_ADDR"]
        //並經過 CheckIP( ) 的驗證
        trueIP = CheckIP(Request.ServerVariables["REMOTE_ADDR"])?
            Request.ServerVariables["REMOTE_ADDR"]:"";
    }

    return trueIP;
}

/// <summary>
/// 檢查 IP 是否合法
/// </summary>
/// <param name="strPattern">需檢測的 IP</param>
/// <returns>true:合法 false:不合法</returns>
    
private bool CheckIP(string strPattern)
{
    // 繼承自:System.Text.RegularExpressions
    // regular: ^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$
    Regex regex = new Regex("^\\d{1,3}[\\.]\\d{1,3}[\\.]\\d{1,3}[\\.]\\d{1,3}$");
    Match m = regex.Match(strPattern);

    return m.Success;
}


參考文章:
01:[hidotnet]真正的取真实IP地址及利弊
02:使用HTTP_X_FORWARDED_FOR获取客户端IP的严重后果
03:PHP利用HTTP_X_FORWARDED_FOR抓取訪客ip
04:How to get client “IP Address” using Asp.net /C#

沒有留言:

張貼留言