[Security]SQL injection的簡介與預防

  • 40044
  • 0
  • 2011-12-20

[Security]SQL injection的簡介與預防

前言
『程式不是會動就好!』,在安全性上的設計,更是完全的貼切這句話。如果設計出在裸奔的網站(請參考黑大的文章),就算網站上的功能可以正常運作,但千瘡百孔的網站,可能淪為人家的玩物或是提款機。

SQL injection的問題,在前幾年掀起了不少次旋風,甚至包括現在可能還有一堆網站有著同樣的問題,要命的是,根本不需要到駭客等級,就可以把有SQL injection問題的網站搞掛。一個程式設計師寫出來的程式,最要不得的就是有SQL injection的問題,我個人認為那是程式設計師不夠專業,甚至不夠格的表現。這不需要多高深的技巧跟學問,要嘛就是程式設計師偷懶,要嘛就是還不夠專業。

這篇文章簡介一下,SQL injection的基本原理,以及基本的防範措施。

什麼是SQL injection
回到之前重構系列的第一篇文章:[ASP.NET]重構之路系列v1 – UI, Business logic, Data access概念分開

畫面:


需求:
當輸入的帳號密碼,在資料庫中有吻合的資料,代表帳號密碼是對的。(再強調一次,一般設計驗證密碼的部分,應該是透過畫面上輸入密碼的值進行hash,再與資料庫中該帳號所對應hash完的密碼比對,以保障密碼不是以明文存在於資料庫中,確保密碼無法被還原)

直覺設計且造成SQL injection問題的程式碼:

 

    protected void Verify_Click(object sender, EventArgs e)
    {
        string connectionString = @"myConnectionString";
        int count;
        using (SqlConnection cn = new SqlConnection(connectionString))
        {
            cn.Open();
            string sqlStatement = @"Select Count(1) From SomeTable Where ID= '" + this.Id.Text + "' AND Password= '" + this.Password.Text + "'";
            SqlCommand sqlCommand = new SqlCommand(sqlStatement, cn);
            count = (int)sqlCommand.ExecuteScalar();
        }

        this.Result.Text = (count > 0) ? "Pass" : "帳號或密碼錯誤";
    }



這樣的程式碼會有什麼問題? 問題可大了! 假設我是壞人,知道或猜測這功能的SQL是這樣串的(大部分功能都很好猜的,猜錯了也不會被咬),那麼我只要在畫面上的Id,隨便輸入個值,接著在密碼的部分,輸入' OR 1=1; --

這時候,這一支聰明的功能就可以把完整的SQL statement組出來,變成:

 

Select Count(1) From SomeTable Where ID= '隨便輸入' AND Password ='' OR 1=1; --

輸入這樣的值代表什麼,代表所有畫面上的輸入值都無所謂,因為1=1恆等式一定會成立,且前面的condition為OR,所以可以把前面的where condition都忽略掉。後面再補上個分號,代表這一個SQL子句已經結束,最後再補上--,代表後面的sql為註解。

就這樣,最基本的SQL injection就成功了。是的,這個網站在裸奔了。

如果你以為,只有登入頁面會存在這樣的問題,或是這樣的問題影響不大,那就大錯特錯了。通常會寫出這樣程式的人,如果又都是自己設計,那洞就不只一個。首先,DB的存取帳號,可能就是預設的sa或admin,也就是權限開到最大。這時候的SQL injection要進DB做事,就跟進自己家廚房一樣,例如:


 

 

Select Count(1) From SomeTable Where ID= '隨便輸入' AND Password ='' OR 1=1; DROP DATABASE pubs --

恭喜你,你DB中的pubs這個DataBase就被Drop掉了。

駭客高興的話,也可以針對sysobjects跟syscolumns來瞭解你DB中有哪些table, 欄位,再比對一下網頁的資料,接著透過Update的語法,把相關的欄位資料加入一些html tag或惡意的程式碼,進而引發從SQL injection觸發的Cross-site Scripting(XSS攻擊),常見的結果就是連到那一頁時,可能被導到別頁,或是XX政府網站上被掛上特殊的國旗,再來就是莫名其妙的個資外洩。

這些情況充斥在我們的生活中,這一切的原罪,都是因為程式設計師的偷懶或不專業。

除了上述的例子,還可以怎麼瞭解SQL的組成?以ASP.NET來說,一樣,通常會寫出那樣有問題的程式,web.config可能也沒有設定custom error頁面,所以當程式出錯時,IIS就大剌剌的幫你把程式錯誤的資訊show在網頁上(就是黃頁的那個),要讓錯誤資訊是掛在SQL statement執行錯誤,是一件再簡單不過的事。所以,要瞭解該網頁的輸入和對應SQL的組成,根本不需要駭客等級就可以做。瞭解之後,要啟動SQL injection,也就只是易如反掌的事。

如何避免SQL injection
簡單的說,過濾輸入值。

但,攻擊手法相當多種,要過濾的字元組合可能一個都漏不得,所以自己設定黑名單來進行過濾,是一件非不得以才做的事。如果是用ASP.NET寫,那所有人的強烈建議,就是使用
Parameters

Parameter的用法相當簡單,這也是為什麼我前面會說,寫出SQL injection問題的程式碼,是一種偷懶的行為。
對應剛剛上面的範例,使用Parameter的方式來改寫:

 

    protected void Verify_Click(object sender, EventArgs e)
    {
        string connectionString = @"myConnectionString";
        var id = this.Id.Text;
        var password = this.Password.Text;

        int count;
        using (SqlConnection cn = new SqlConnection(connectionString))
        {
            cn.Open();
            string sqlStatement = @"Select Count(1) From SomeTable Where ID= @id AND Password= @password";
            SqlCommand sqlCommand = new SqlCommand(sqlStatement, cn);
            
            ////定義parameter型別
            sqlCommand.Parameters.Add("@id", SqlDbType.VarBinary);
            sqlCommand.Parameters["@id"].Value = id;
            
            ////讓ADO.NET自行判斷型別轉換
            sqlCommand.Parameters.AddWithValue("@password", password);

            count = (int)sqlCommand.ExecuteScalar();
        }

        this.Result.Text = (count > 0) ? "Pass" : "帳號或密碼錯誤";
    }



透過使用parameter,在SqlCommand執行時,會自動過濾掉可能造成問題的字元。

另外,SQL injection並不只會發生在where condition,也有可能發生在Order by等語法中。所以只要是畫面上可能被改變的值,要串到SQL語法中,就應該使用parameter。如果是Parameter無法使用的語法,例如Table的名字,那也應該自己建立一個mapping module,來避免畫面上user可任意輸入且可能為惡意攻擊碼的值,直接存進DB中或被非預期地執行。

結論
在patterns & practices Developer Center簡單歸納出三個步驟防止SQL injection:

  1. Constrain input.
  2. Use parameters with stored procedures.
  3. Use parameters with dynamic SQL.


補充

  1. web.config的custom error要記得打開,避免讓『使用者』看到詳細的錯誤訊息
  2. 權限這種東西,都是能開越小越好,包括資料庫。
  3. 所謂畫面可能被改變的值,可能包括了Url上的QueryString,post過來的表單資料,甚至於cookie內容假造。
  4. 除非別無選擇,請不要自己用filter黑名單的方式來阻擋XSS或SQL injection。原因:
    1. 自己想的黑名單再多,也沒有被全世界使用這麼久的library來得妥當。既然全世界寫.NET的人幾乎都用parameter在阻擋SQL injection,有這麼多人當墊背,就盡量別自己假掰。
    2. 就算.NET的Library真的被攻破了,有全世界加整個微軟的工程師在替我們緊張,基本上這種等級的問題,都是一天內就會有work around,下一波的patch就會修正掉。何樂而不為,應該把時間花在真正有價值的地方。


Reference

1.SqlCommand.Parameters 屬性
2.技巧和訣竅:防範SQL注入攻擊
3.SQL Injection(資料隱碼)的源由與防範之道
4.你的網站正在裸奔嗎?
5.驚見SQL Injection攻擊大海嘯-LizaMoon
6.游擊式的SQL Injection攻擊
7.How To: Protect From SQL Injection in ASP.NET
8.CustomError 類別


blog 與課程更新內容,請前往新站位置:http://tdd.best/