記一次Sql注入 解決方案

老大反饋代碼里面存在sql注入错览,這個漏洞會導致系統遭受攻擊纫雁,定位到對應的代碼,如下圖所示

image

like 進行了一個字符串拼接倾哺,正常的情況下轧邪,前端傳一個 cxk 過來,那么執(zhí)行的sql就是

select * from test where name like '%cxk%';

好像沒有什么問題羞海,但是忌愚,如果被攻擊了傳了個 cxk%'; DELETE FROM test WHERE name like '%cxk

那么 這條sql 將會拼接成


select * from test where name like '%cxk%'; DELETE FROM test WHERE name like '%cxk%';

執(zhí)行不會報錯,結果是 name like cxk 的數據全部刪除扣猫,這個還是比較溫柔的sql注入菜循,如果是 drop table ,那不是要原地爆炸?

既然Sql 注入危害這么大申尤,那么怎么防范呢癌幕?

采用sql語句預編譯和綁定變量,是最簡單昧穿,也是最有效的方案.

那什么是預編譯呢勺远?

like this

 select  * from test where name like ?  args: WrappedArray(JdbcValue(%cxk%))
 

先是 select * from test where name like ? 這樣預編譯好,然后傳進來的數據以參數化的形式執(zhí)行sql,就可以防止sql 注入时鸵。

為什么這樣可以防止sql 注入呢胶逢?

分別給兩種場景測試

1.likeString

代碼如下


def likeString(name:String) ={
    dataSource.rows[Test](sql" select  * from test where name like  "+s"'%${name}%'")
  }
  

輸入 cxk%'; DELETE FROM test WHERE name like '%cxk

打斷點可以看到

image

將statement 中的值復制出來 到navicat 中,可以看到

image

那么jdbc 執(zhí)行就會 直接執(zhí)行饰潜,然后把cxk 刪了初坠。

2.like

代碼如下

 def like(name:String) ={
    // cxu
    // %cku%
    dataSource.rows[Test](sql" select  * from test where name like ${name.likeSql}")
  }
  
  
  implicit class StringBuildSqlLikeImplicit(s:String){


    def likeSql: String ={
      s"%${s}%"
    }

    def likeLeftSql: String = {
       s"%${s}"
    }

    def likeRightSql: String ={
      s"${s}%"
    }

  }
  


輸入 cxk%'; DELETE FROM test WHERE name like '%cxk

打斷點可以看到

image
image

可以看到 statement =   select  * from test where name like ** NOT SPECIFIED **


PreparedStatement.setString(1,'cxk%'; DELETE FROM test WHERE name like '%cxk')

之后,

statement = 
select  * from test where name like '%cxk\'; DELETE FROM test WHERE name like \'%cxk%'

將statement 中的值復制出來 到navicat 中彭雾,可以看到

image

string 內部的; % 被格式化碟刺, 這樣執(zhí)行的話,內部的sql 就以字符串的形式存在薯酝,這樣避免了Sql 注入半沽。

那 PreparedStatement 是這么做到的呢, 主要的原因是 PreparedStatement.setString(int parameterIndex, String x)

這里面會對x 進行一個格式化

判斷是否需要格式化

image

貼上源碼


    private boolean isEscapeNeededForString(String x, int stringLength) {
        boolean needsHexEscape = false;

        for (int i = 0; i < stringLength; ++i) {
            char c = x.charAt(i);

            switch (c) {
                case 0: /* Must be escaped for 'mysql' */

                    needsHexEscape = true;
                    break;

                case '\n': /* Must be escaped for logs */
                    needsHexEscape = true;

                    break;

                case '\r':
                    needsHexEscape = true;
                    break;

                case '\\':
                    needsHexEscape = true;

                    break;

                case '\'':
                    needsHexEscape = true;

                    break;

                case '"': /* Better safe than sorry */
                    needsHexEscape = true;

                    break;

                case '\032': /* This gives problems on Win32 */
                    needsHexEscape = true;
                    break;
            }

            if (needsHexEscape) {
                break; // no need to scan more
            }
        }
        return needsHexEscape;
    }


可以看到 它會對傳進來的參數判斷吴菠,如果含有一些非法字符會判斷傳過來的值需要格式化者填, 那它是怎么格式化的呢? 我們看下源碼

// setString  里的部分源碼

  if (this.isLoadDataQuery || isEscapeNeededForString(x, stringLength)) {
                    needsQuoted = false; // saves an allocation later

                    StringBuilder buf = new StringBuilder((int) (x.length() * 1.1));

                    buf.append('\'');

                    //
                    // Note: buf.append(char) is _faster_ than appending in blocks, because the block append requires a System.arraycopy().... go figure...
                    //

                    for (int i = 0; i < stringLength; ++i) {
                        char c = x.charAt(i);

                        switch (c) {
                            case 0: /* Must be escaped for 'mysql' */
                                buf.append('\\');
                                buf.append('0');

                                break;

                            case '\n': /* Must be escaped for logs */
                                buf.append('\\');
                                buf.append('n');

                                break;

                            case '\r':
                                buf.append('\\');
                                buf.append('r');

                                break;

                            case '\\':
                                buf.append('\\');
                                buf.append('\\');

                                break;

                            case '\'':
                                buf.append('\\');
                                buf.append('\'');

                                break;

                            case '"': /* Better safe than sorry */
                                if (this.usingAnsiMode) {
                                    buf.append('\\');
                                }

                                buf.append('"');

                                break;

                            case '\032': /* This gives problems on Win32 */
                                buf.append('\\');
                                buf.append('Z');

                                break;

                            case '\u00a5':
                            case '\u20a9':
                                // escape characters interpreted as backslash by mysql
                                if (this.charsetEncoder != null) {
                                    CharBuffer cbuf = CharBuffer.allocate(1);
                                    ByteBuffer bbuf = ByteBuffer.allocate(1);
                                    cbuf.put(c);
                                    cbuf.position(0);
                                    this.charsetEncoder.encode(cbuf, bbuf, true);
                                    if (bbuf.get(0) == '\\') {
                                        buf.append('\\');
                                    }
                                }
                                // fall through

                            default:
                                buf.append(c);
                        }
                    }

                    buf.append('\'');

                    parameterAsString = buf.toString();
                }

從這里可以看出做葵,會講' 加一個'' 那么原傳入的Sring 就會被格式化成上文所說占哟。
打斷點我們可以看到

image

這一塊是jdbc PreparedStatement 對SQL注入的防范。

從網上我還看到了一些這樣的

那么,什么是所謂的“precompiled SQL statement”呢榨乎?

回答這個問題之前需要先了解下一個SQL文在DB中執(zhí)行的具體步驟:


1.Convert given SQL query into DB format -- 將SQL語句轉化為DB形式(語法樹結構)
2.Check for syntax -- 檢查語法
3.Check for semantics -- 檢查語義
4.Prepare execution plan -- 準備執(zhí)行計劃(也是優(yōu)化的過程嗓化,這個步驟比較重要,關系到你SQL文的效率谬哀,準備在后續(xù)文章介紹)
5.Set the run-time values into the query -- 設置運行時的參數
6.Run the query and fetch the output -- 執(zhí)行查詢并取得結果

出自 PreparedStatement是如何防止SQL注入的

打斷點調試的時候看到,PreparedStatement 最終還是會轉化成statement 然后執(zhí)行严肪,
jdbc 這么做應該是做應該是為了 mysql 的緩存機制史煎,我們知道,mysql 進行select 查詢的時候驳糯,會有一個緩存機制篇梭,如果執(zhí)行語句一致的話,就會拿mysql 的緩存直接獲取數據酝枢,如果以參數形式傳到mysql 的話恬偷,這樣就沒有辦法命中緩存了(個人看法,錯誤請佐證)帘睦。

綜上所述袍患,SQL注入,用PreparedStatement 防治是可以防治的竣付,代碼中也盡量用 PreparedStatement 這種形式诡延。

題外話,那么這個是jdbc 的做法古胆,那其他的框架是怎么解決SQL 注入的呢?

PHP 防治Sql注入

1.通過函數去對一些特殊字符進行處理 例如 addslashes(str) 肆良,mysql_escape_string(str)

2.預編譯的做法

Node 防治SQL 注入

1.使用escape()對傳入參數進行編碼,

2.使用connection.query()的查詢參數占位符:(預編譯)

3.使用escapeId()編碼SQL查詢標識符:

4.使用mysql.format()轉義參數:

參考文章 node-mysql中防止SQL注入

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末逸绎,一起剝皮案震驚了整個濱河市惹恃,隨后出現的幾起案子,更是在濱河造成了極大的恐慌棺牧,老刑警劉巖巫糙,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異陨帆,居然都是意外死亡曲秉,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門疲牵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來承二,“玉大人,你說我怎么就攤上這事纲爸『ヰ” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長负蚊。 經常有香客問我神妹,道長,這世上最難降的妖魔是什么家妆? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任鸵荠,我火速辦了婚禮,結果婚禮上伤极,老公的妹妹穿的比我還像新娘蛹找。我一直安慰自己,他們只是感情好哨坪,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布庸疾。 她就那樣靜靜地躺著,像睡著了一般当编。 火紅的嫁衣襯著肌膚如雪届慈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天忿偷,我揣著相機與錄音金顿,去河邊找鬼。 笑死牵舱,一個胖子當著我的面吹牛串绩,可吹牛的內容都是我干的。 我是一名探鬼主播芜壁,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼礁凡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了慧妄?” 一聲冷哼從身側響起顷牌,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎塞淹,沒想到半個月后窟蓝,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡饱普,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年运挫,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片套耕。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡谁帕,死狀恐怖,靈堂內的尸體忽然破棺而出冯袍,到底是詐尸還是另有隱情匈挖,我是刑警寧澤碾牌,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站儡循,受9級特大地震影響舶吗,放射性物質發(fā)生泄漏。R本人自食惡果不足惜择膝,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一誓琼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肴捉,春花似錦踊赠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽今穿。三九已至缤灵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蓝晒,已是汗流浹背腮出。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留芝薇,地道東北人胚嘲。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像洛二,于是被迫代替她去往敵國和親馋劈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內容