<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/tips/5118

            0x00 前言


            這個系列教程我本來打算的是翻譯,后來過了一下文章發現教學過程不是很友好,所以大體是按他的思路,不過其中做了很多改動,還有個事情就是我假定讀者已經了解基礎的python和SQL注入的知識。還有一個需要注意的是我是寫在ipython notebook中,所以文中的代碼可能需要一點改動才能用。

            我覺得這篇文章的簡要的主題就是,給"如何識別sql注入" 提供一種思路,這個思路的本身就是用數據科學的形式來解決問題,其實就是所謂的機器學習。

            為了達到我們的目標就需要一個過程:

            0x01 準備


            1. tools


            這個系列主要以python為主,所以下面的是所需的python庫,我不會教你怎么安裝這些東西。

            sqlparse (一個用于解析sql語法樹的庫) Scikit-Learn (python機器學習庫) Pandas (用于快速處理一定量的數據) numpy (用于科學計算) matplotlib (用于數據可視化)

            2. 什么是機器學習?


            因為本文中用的是監督學習,那么我們會注入監督學習所需要的知識,機器學習顧名思義就是讓機器具備學習的能力,假設我們已經有了一個算法能夠進行學習,那么我們該如何教給它知識,假設一個小孩,我們需要讓它知道如何辨認水果,我們就會放兩堆不同的水果,告訴他左邊的是蘋果,右邊的是香蕉。然后等到他學習了這堆狗屎玩意,我們就可以帶著他去看一堆新的水果讓后讓他自己進行辨認了。 換句話說我們這次就是要準備一堆的數據,告訴算法,左邊的是正常的sql請求,右邊的是sql注入的請求,讓后讓他進行學習,最后我們再給他一堆未知的數據進行測試。

            3. SQL語法樹


            你覺得sql語言從輸入數據庫到放回內容都經過了怎樣的處理,sql語言是一種DSL(領域特定語言),比如ruby,c,java,這些可以做任何事,但有一些語言只能做某個領域的事,sql就是這樣一種語言,它只能描述對于數據的操作。但是它在大歸類的時候是被歸類到編程語言里的,就需要經過詞法分析再到語法分析,對于這個過程不了解的同學可以看。 http://zone.wooyun.org/content/17006

            0x02 準備數據


            因為這次的數據已經準備好了,所以我們所需要就是寫個小腳本把他讀取出來,所需要的東西我會進行打包。

            下載地址:下載

            #!python
            # -*- coding: utf-8 -*-
            import os
            import pandas as pd
            
            basedir = '/Users/slay/project/python/datahack/data_hacking/sql_injection/data'
            filelist = os.listdir(basedir)
            df_list = []
            
            # 循環讀取 basedir下面的內容,文件名為 'legit'的是合法內容,malicious的是 惡意sql語句
            for file in filelist:
                df = pd.read_csv(os.path.join(basedir,file), sep='|||', names=['raw_sql'], header=None)
                df['type'] = 'legit' if file.split('.')[0] == 'legit' else 'malicious'
                df_list.append(df)
            
            # 將內容放入 dataframe對象
            dataframe = pd.concat(df_list, ignore_index=True)
            
            dataframe.dropna(inplace=True)
            
            # 統計內容
            print dataframe['type'].value_counts()
            
            # 查看前五個
            dataframe.head()
            

            enter image description here

            我們現在可以清楚的知道我們面臨的是一堆什么樣的數據了。

            0x03 特征工程


            1. 概念


            So,然后呢?我們是不是就可以把數據丟進算法里然后得到一個高大上的sql防火墻了?那么我們現在來想一個問題,我們有兩個sql語句,從admin表中查看*的內容。

            #!sql
            select user from admin;
            select hello from admin;
            

            算法最后得到的輸入是什么,是[1,1,0,1,1] 和 [1,0,1,1,1] 沒看懂沒關系,就是說得到了這樣的東西。

            {select:1, user:1, hello:0, from:1, admin:1} {select:1, user:0, hello:1, from:1, admin:1}

            是不是哪里不對,就是說在機器看來 user 和 hello 在本質來看是屬于不同的類型的玩意,但是對于了解sql語言本身的你知道他們是一樣的東西,所以我們就需要給同一種東西打一個標簽讓機器能夠知道。

            那么是否對什么是特征工程有了一些模糊的了解?要做好特征工程,就需要對于你所面臨的問題有著深刻的了解,就是“領域知識”,帶入這個問題就像你對于sql語言的了解,在這個了解的基礎上去處理特征,讓算法更能將其分類。帶入水果分類問題就是,你得告訴小孩,香蕉是長長的,黃色的,蘋果是紅色的,圓圓的,當然,如果你直接把上面的玩意丟進算法里頭,分類器也是可以工作的,準確度大概能過 70%,也許你看起來還行,當是我只能告訴你這是個災難。這讓我想起某次數據挖掘的競賽,第一名和第一千名的分差是0.01,這群變態。

            2. 轉化數據


            所以現在我們需要的就是將原始數據轉化成特征,這就是為什么我剛才說到語法樹的,我們需要對sql語句進行處理,對同一種類型的東西給予同一種標示,現在我們使用sqlparse 模塊建立一個函數來處理sql語句。

            #!python
            import sqlparse
            import string
            
            def parse_sql(raw_sql):
                parsed_sql = []
                sql = sqlparse.parse(unicode(raw_sql,'utf-8'))
                for parse in sql:
                    for token in parse.tokens:
                        if token._get_repr_name() != 'Whitespace':
                                parsed_sql.append(token._get_repr_name())
                return parsed_sql
            
            sql_one = parse_sql("select 2 from admin")
            sql_two = parse_sql("INSERT INTO Persons VALUES ('Gates', 'Bill', 'Xuanwumen 10', 'Beijing')")
            
            print "sql one :%s"%(sql_one)
            print "sql two :%s"%(sql_two)
            

            輸出 sql one :['DML', 'Integer', 'Keyword', 'Keyword'] sql two :['DML', 'Keyword', 'Identifier', 'Keyword', 'Parenthesis']

            我們可以看到 select 和 insert都被認定為 dml,那么現在我們要做的就是觀測數據,就是查看特征是否擁有將數據分類的能力,現在我們先對sql語句進行轉換。

            #!python
            dataframe['parsed_sql'] = dataframe['raw_sql'].map(lambda x:parse_sql(x))
            dataframe.head()
            

            enter image description here

            3. Other


            理論上我們現在就可以直接把這些東西扔進算法中,不過為了方便我在說點別的,分類器的性能很大程度上取決于特征,假設這些無法很好的對數據進行分類,那我們就需要考慮對特征進行一些別的處理,比如你覺得sql注入的話sql語句貌似都比較長,那么可以將其轉化成特征。

            #!python
            dataframe['len'] = dataframe['parsed_sql'].map(lambda x:len(x))
            dataframe.head()
            

            enter image description here

            現在我們需要觀測下數據,看看長度是否有將數據進行分類的能力。

            #!python
            %matplotlib inline
            import matplotlib.pyplot as plt
            dataframe.boxplot('len','type')
            plt.ylabel('SQL Statement Length')
            

            enter image description here

            0x04 機器學習


            1. Train & Test


            這里我就直接調用python庫了,因為解釋起來很麻煩,而且就我對于這次要使用的隨機森林(Random Forest)的了解層度,我覺得還不如不講,對于其數學原理有興趣的可以參考下面的paper,是我見過對隨機森林解釋的最清楚的。

            Gilles Louppe《隨機森林:從理論到實踐》 http://arxiv.org/pdf/1407.7502v1.pdf

            接下來我們再對特征做一次處理,轉換成0和1的向量形式,x是我們的特征數據,y表示結果。

            #!python
            import numpy as np
            from sklearn.preprocessing import LabelEncoder
            from sklearn.feature_extraction.text import CountVectorizer
            import string
            
            vectorizer = CountVectorizer(min_df=1)
            le = LabelEncoder()
            
            X = vectorizer.fit_transform(dataframe['parsed_sql'].map(lambda x:string.join(x,' ')))
            
            x_len = dataframe.as_matrix(['len']).reshape(X.shape[0],1)
            
            x = X.toarray()
            
            y = le.fit_transform(dataframe['type'].tolist())
            
            print x[:100]
            print y[:100]
            

            輸出

            [[0 0 0 ..., 2 0 0]
             [0 0 0 ..., 1 0 0]
             [0 0 0 ..., 0 0 0]
             ..., 
             [0 0 0 ..., 0 0 0]
             [0 0 0 ..., 0 0 0]
             [0 0 0 ..., 0 0 0]]
            [1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
             0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
             0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
            

            輸入

            #!python import sklearn.ensemble 
            
            clf = sklearn.ensemble.RandomForestClassifier(n_estimators=30) 
            
            scores = sklearn.cross_validation.cross_val_score(clf, x, y, cv=10, n_jobs=4) 
            
            print scores
            

            輸出

            [ 0.97699497  0.99928109  0.99928058  1.          1.          0.97192225
              0.99928006  0.99856012  1.          1.        ]
            

            上面的cross_validation是我們測試分類器的一種方法,原理就是把訓練后的分類器在一些分割后的數據集上測試結果,從得出的多個評分中可以更好的評估性能,我們得出了一個貌似不錯的結果,接下來讓我們訓練分類器

            #!python
            from sklearn.cross_validation import train_test_split
            # 將數據分割為 訓練數據 和 測試數據,訓練數據用于訓練模型,測試數據用于測試分類器性能。
            X_train, X_test, y_train, y_test, index_train, index_test = train_test_split(x, y, dataframe.index, test_size=0.2)
            # 開始訓練
            clf.fit(X_train, y_train)
            # 預測
            X_pred = clf.predict(X_test)
            

            如果剛才那些數值無法直觀的看出你訓練了個什么玩意出來,那么你就需要一個混淆矩陣。

            #!python
            %matplotlib inline
            import matplotlib.pyplot as plt
            from sklearn.metrics import confusion_matrix
            
            cm = confusion_matrix(X_pred,y_test)
            print cm
            
            # Show confusion matrix in a separate window
            plt.matshow(cm)
            plt.title('Confusion matrix')
            plt.colorbar()
            plt.ylabel('True label')
            plt.xlabel('Predicted label')
            plt.show()
            

            enter image description here

            混淆矩陣可以更加直觀的讓我們觀察數據,我們的數據氛圍 0,1兩類,比如 [0,0]=196 就是legit被正確分類的樣本,[0,1]=3是被錯誤分類的樣本,那么第二行就是惡意樣本分類的情況。

            現在我們看起來分類起似乎工作的不錯,達到了99%的正確率,可是你想象這個問題,每199個正確樣本就有3個被錯誤分類,一般來說一個中型的網站需要處理的sql語句就可能會達到 上面的1000倍,就是說你可能會有3000個無害的語句被攔截。所以下面我們需要的是降低legit被錯誤分類的概率。

            2. 調整


            sklearn大部分的模型有個功能叫predict_proba,就是說預測的概率,predict其實就是內部調用下predict_proba,然后按50%。我們可以裝變一下直接調用predict_proba,讓我們自己調整分類的概率。

            #!python
            loss = np.zeros(2)
            y_probs = clf.predict_proba(X_test)[:,1]
            thres = 0.7 # 用0.7的幾率來分類
            y_pro = np.zeros(y_probs.shape)
            y_pro[y_probs>thres]=1.
            cm = confusion_matrix(y_test, y_pro)
            print cm
            

            輸出

            [[ 197    0]
             [   5 2577]]
            

            legit被錯誤分類的概率降低了,但是0.7只是我們隨意想出來的一個參數,能不能簡單的想個辦法優化一下呢?讓我們簡單定義一個函數f(x),會隨著我們輸入的參數輸出誤分類的概率。

            #!python
            def f(s_x):
                loss = np.zeros(2)
                y_probs = clf.predict_proba(X_test)[:,1]
                thres = s_x # This can be set to whatever you'd like
                y_pro = np.zeros(y_probs.shape)
                y_pro[y_probs>thres]=1.
                cm = confusion_matrix(y_test, y_pro)
                counts = sum(cm)
                count = sum(counts)
                if counts[0]>0:
                    loss[0]=float(cm[0,1])/count
                else:
                    loss[0]=0.01
                if counts[1]>0:
                    loss[1]=float(cm[1,0])/count
                else:
                    loss[1]=0.01
                return loss
            
            # 0.1 到 0.9 之前的 100個數值
            x = np.linspace(0.1,0.9,100)
            # x輸入f(x)之后得到的結果
            y = np.array([f(i) for i in x])
            # 可視化
            plt.plot(x,y)
            plt.show()
            

            enter image description here

            額,繼續用0.7吧。

            0x05 結語


            這是個系列,可能我這么說也沒人信吧,中途開始就有點亂了。

            上一句老話吧,也不知道誰說的,反正大家天天掛嘴邊。

            數據挖掘項目的表現,80%取決于特征工程,剩下的20%才取決于模型等其他部分;又說數據挖掘項目表現的上限由特征工程決定,而其接近上限的程度,則由模型決定。

            source:http://nbviewer.ipython.org/github/ClickSecurity/data_hacking/blob/master/sql_injection/sql_injection.ipynb

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线