SQL手工注入基礎篇
0.前言
本篇博文是對SQL手工注入進行基礎知識的講解,更多進階知識請參考進階篇(咕咕),文中有誤之處,還請各位師傅指出來。學習本篇之前,請先確保以及掌握了以下知識:
- 基本的SQL語句
- HTTP的GET、POST請求,URL編碼
文中所有例題選自sqlilab,可以先配置好一起邊看邊操作。因為虛擬機器炸了,所以我自己搭建了一個簡陋的平臺,sqlilab可自行進行搭建練習,在後面的部落格也會寫一些關於sqlilab的wp。
1.準備工作
在開始SQL注入之前,我們首要需要了解SQL注入的原理,對於一個新安裝好的MySql資料庫,你至少會包含三個已經建立好的資料庫分別是user、infomation_schema、performance_schema如下圖所示
而SQL注入要乾的,這些系統資料庫中儲存了MySql各個資料庫的屬性以及使用者資訊,我們要做的就是繞過過濾再來利用這些系統表進行查詢。
瞭解了這個之後我們還要了解一點就是PHP的GET方法和POST方法傳參的區別,如果使用GET方法,則會自動進行一次url解碼,例如傳入%23實際得到‘#’,而POST則會將資料原封不動的傳輸。下面我們開始進入正式的SQL注入階段。
2.判斷注入型別
一般的對於SQL的查詢語句,有字元型和數值型查詢,而這兩者的區別就是是否有單引號,這決定了我們接下來應該如何構造SQL注入語句。
判斷方法有如下幾種,
- +-數值,如果是數值型的,你可以嘗試使用1+1,1+2,這樣的語句,例如 看網頁回顯是否正確。
- and 1=1,and 1=2,直接在後面新增“1 and 1=1”和“1 and 1=2”(前面有個空格)來進行查詢,若1=1回顯正確而1=2回顯錯誤則為數值型。
- 加‘#,在後面新增'#進行查詢,若回顯正確則表明為字元型。
除了上述幾種方法還可以用其他方法進行判斷,但原理都是構造SQL語句進行判斷。
3.查列數
通過上述方法知道了注入型別之後,我們就可以進行下一步的操作了,在這裡我搭建了一個簡易的存在字元型查詢漏洞的頁面,大家可以在本地搭建一下一邊學習一邊練習。
SQL程式碼如下:
1 /* 2 Navicat MySQL Data Transfer 3 4 Source Server : Mysql 5 Source Server Version : 50553 6 Source Host : localhost:3306 7 Source Database : test 8 9 Target Server Type : MYSQL 10 Target Server Version : 50553 11 File Encoding : 65001 12 13 Date: 2019-09-18 23:17:53 14 */ 15 16 SET FOREIGN_KEY_CHECKS=0; 17 18 -- ---------------------------- 19 -- Table structure for secret_table 20 -- ---------------------------- 21 DROP TABLE IF EXISTS `secret_table`; 22 CREATE TABLE `secret_table` ( 23 `fl4g` varchar(32) DEFAULT NULL 24 ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 25 26 -- ---------------------------- 27 -- Records of secret_table 28 -- ---------------------------- 29 INSERT INTO `secret_table` VALUES ('flag_is_here'); 30 31 -- ---------------------------- 32 -- Table structure for student 33 -- ---------------------------- 34 DROP TABLE IF EXISTS `student`; 35 CREATE TABLE `student` ( 36 `id` int(11) NOT NULL AUTO_INCREMENT, 37 `name` varchar(255) DEFAULT NULL, 38 `class` varchar(255) DEFAULT NULL, 39 `age` int(11) DEFAULT NULL, 40 `note` varchar(16) DEFAULT '', 41 PRIMARY KEY (`id`) 42 ) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8; 43 44 -- ---------------------------- 45 -- Records of student 46 -- ---------------------------- 47 INSERT INTO `student` VALUES ('1', 'zhangsan', 'nss', '18', ''); 48 INSERT INTO `student` VALUES ('2', 'lisi', 'nss', '19', ''); 49 INSERT INTO `student` VALUES ('3', 'wangwu', 'nss2', '20', ''); 50 INSERT INTO `student` VALUES ('4', 'zhaoliu', 'nss2', '21', ''); 51 INSERT INTO `student` VALUES ('5', 'sunqi', 'nss2', '22', ''); 52 INSERT INTO `student` VALUES ('6', 'qianba', 'nss', '23', ''); 53 INSERT INTO `student` VALUES ('7', 'liujiu', 'nss', '24', '');
PHP程式碼如下:
1 <?php 2 if (!empty($_POST['st'])) { 3 $conn = mysqli_connect("localhost","root","root","test"); 4 $name = $_POST['name']; 5 $sql = "select * from student where name='".$name."';"; 6 7 $result = mysqli_query($conn,$sql); 8 9 echo "<table border='1'> 10 <tr> 11 <th>Id</th> 12 <th>Name</th> 13 <th>Class</th> 14 <th>Age</th> 15 <th>Note</th> 16 </tr>"; 17 18 while($row = mysqli_fetch_array($result)) { 19 echo "<tr>"; 20 echo "<td>" . $row['id'] . "</td>"; 21 echo "<td>" . $row['name'] . "</td>"; 22 echo "<td>" . $row['class'] . "</td>"; 23 echo "<td>" . $row['age'] . "</td>"; 24 echo "<td>" . $row['note'] . "</td>"; 25 echo "</tr>"; 26 } 27 echo "</table>"; 28 29 mysqli_close($conn); 30 } 31 ?> 32 33 <!doctype html> 34 <!-- flag in SQL --> 35 <form action="" method="POST"> 36 <input type="text" placeholder="Input A Name" name="name" value=""> 37 <button type="submit" name="st" value='1'>提交</button> 38 </form> 39 <div style="position: absolute;bottom:0;width:100%;display:flex;flex-direction:column;"> 40 <hr > 41 <a style="align-self:center;" href="./source.txt">Source</a> 42 </div>View Code
通過上面的判斷我們知道是字元型注入,現在我們需要查出這個資料表的列數來為後面的聯合查詢做鋪墊。
當查詢語句最後為where xx 的時候我們使用order by num;
當查詢語句最後為limit xx的時候我們使用into @,@;
對於第一種order by num; num代表數值,語義就是以第幾列進行排序,當列不存在是就會報錯,我們就可以用二分的方法找出正確的列數。如下
對於lisi' order by 6#,lisi是資料庫中的正常資料,單引號是為了閉合前面的select語句,#是mysql的單行註釋語句,提示報錯,換成5則正確,說明該表有5列。
對於limit xx的情況,我會在另一篇額外講解。
4.確定欄位位置
當我們獲得表的列數之後,就可以通過聯合查詢獲得資料庫的資訊,但在此之前,我們還需要確定每個欄位顯示在網頁上的位置,方便檢視後面的資料。
對於例題,我們知道列數為5之後,構造引數lisi' and 1=2 union select 1,2,3,4,5#即可知道每個欄位的位置
完整的SQL語句就是select * from student where name = ‘lisi’ and 1=2 union select 1,2,3,4,5#';
and 1=2是為了避免一些只顯示一行的頁面過濾掉後面的聯合查詢的資料,所以讓前面的查詢條件永遠為false,這裡你不加這個,但為了方便我們都加上這個。
union就是合併操作,完整語義就是使用union合併兩個select的結果集,前者為空,後者結果為1,2,3,4,5,所以就會把這些結果合併然後顯示到對應的欄位。
5.獲取資料庫資訊
當列數和欄位位置都知道後,我們就可以通過進一步的查詢來獲取資料庫資訊了,下面提供一些常用的資料庫函式。
database():檢視當前資料庫名稱
version():檢視資料庫版本資訊
user():返回當前資料庫連線的使用者
char():將ASCII碼轉化成字元,用於分隔每個欄位的內容
使用方法就是將函式放在列的位置,例如提交lisi' and 1=2 union select 1,user(),database(),4,5#,結果如下:
同樣在最開始的時候我們提到了MySql的幾個系統表,我們也可以從這些系統表中獲取所有表名,列數,欄位名等資料。
例如查詢所有表名,提交lisi' and 1=2 union select 1,2,3,4,TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='test,結果如下
第五個位置也就是查詢INFORMATION_SCHEMA.TABLES表中資料庫為test的所有表名,MySql的表屬性會儲存在這個表中,因為後面還有一個單引號,所以這裡右邊就不要單引號了。
這樣我們就查詢到了所有的表名,現在我們發現有個表叫做secret_table,猜測flag隱藏在其中,那麼我們再來獲取這個表所有的欄位名。
提交lisi' and 1=2 union select 1,2,3,4,COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='secret_table。結果如下
語義同上,這裡就不在做解釋了,現在我們可以看到這個表有一個叫fl4g的欄位,那flag就藏在這裡沒錯了。
接下來要做的就簡單了,我們只需要提交lisi' and 1=2 union select 1,2,3,4,fl4g from secret_table#
到此我們就找到了最終的flag。
6.總結
SQL手工注入的基礎知識道馳就結束了,推薦看完了這篇再去學習SQLmap的知識,可以很快上手也可以瞭解其本質的東西,不推薦直接學習SQLmap成為指令碼小子。
再梳理一遍上述知識,首先找到注入點,注入點一般是網頁的某個提供查詢的地方,然後確定是字元型還是數值型,當確定了注入型別之後,就是進行確定一些資料表的資訊,方便後面的盲注,最後在從這些注入點得到我們想要的資訊。
對於CTF題目來說,一般不是讓你盲注,會將程式碼給你,這時候注入點肯定是會有諸多的過濾,這時候我們就不能直接執行上述語句了,我們就需要構造一些語句去繞過執行,但最終要達到的效果和上述內容是一致的。
對於更多的SQL注入知識,以及一些繞過技巧我將會在後面的進階篇詳解闡述。
&n