4.1 SQL注入:红队打开数据库的锁


4.1 SQL 注入:红队打开数据库的锁

本节摘要:本节拆解 SQL 注入的成因与最小利用:字符串拼接如何让用户输入改变语句结构,一个引号怎么闭合出"永真条件"绕过登录,联合查询怎么把任意表读出来。演示全部在本地靶场完成,payload 做了最小化裁剪,只求讲清机理。

一个引号引发的越权

注入回合开打。红队没有碰登录口,而是盯着商品搜索框随手输入了一个单引号。页面报错了——不是"没有找到商品"的业务错误,而是数据库语法错误。这个报错本身就是情报:用户输入被原样拼进了 SQL 语句,引号打断了语句结构,数据库把剩下的输入当成了残缺的语法。红队据此知道,这个框不是搜索框,是数据库的遥控器。

这背后的哲学问题值得先想清楚:程序里有两种东西——指令(程序想执行的逻辑)与数据(用户提供的值)。安全的前提是两者边界清晰。注入的本质,就是边界崩塌:数据里夹带的指令碎片,被数据库当真执行了。

成因:数据与指令的边界崩塌

看靶场里那段出问题的代码(反面教材):

# 反面教材:字符串拼接(靶场复现) def search_products(keyword: str): sql = f"SELECT id, name, price FROM products WHERE name LIKE '%{keyword}%'" return db.execute(sql).fetchall()

正常输入"咖啡",语句是自洽的。输入一个引号,语句变成 ... LIKE '%'%'——引号提前闭合,后面的 % 成了语法错误。若输入构造为 ' OR '1'='1,拼出来的语句变成:

-- 拼接结果:条件恒真,返回全表 SELECT id, name, price FROM products WHERE name LIKE '%' OR '1'='1%'

条件从"名字像某关键词"变成"名字像空串 永真",搜索框直接变成全表导出。登录场景同理,经典的闭合绕过(靶场演示,payload 已最小化):

输入用户名:admin'-- 输入密码:随便填 拼接后:SELECT ... FROM users WHERE name='admin'--' AND pwd_hash='...' 效果: -- 把密码校验整段注释掉,直接以 admin 身份登录

两行输入,认证旁路。注意这里的分野:口令回合红队在"猜值",注入回合红队在"改逻辑"——后者不关心你的密码是什么。

04-01-fig01

靶场演示:从报错到读表

红队确认注入点后,标准推进是"问数据库自己要地图":

-- 靶场演示:先探当前库与版本(报错注入样式,最小化) ' AND EXTRACTVALUE(1, CONCAT(0x7e, DATABASE())) -- -- 页面报错信息回显:XPATH syntax error: '~shopdb' -- 同法可问出版本、当前用户、表清单,随后逐表读取

联合查询则把结果"贴"进正常页面:

-- 靶场演示:联合查询读取用户表(列数先用排序子句对齐) ' UNION SELECT name, pwd_hash, NULL FROM users -- -- 搜索结果区直接列出全部账号与口令哈希——上一章的离线破解素材就此到手

这两个演示串起了科目之间的接力:注入拖出哈希列,离线暴破还原口令,横向移动再扩张——这正是第章开篇攻击链的具象化。安全写作边界在此重申:演示止步于"读数据",写文件、执行系统命令这类后渗透动作只讲原理与条件(数据库账号有写权限、目录可写、配置允许),不给落地代码。

危害面:注入能走多远

注入的危害上限由三件事决定:数据库账号权限、数据库进程的文件与命令权限、应用对错误的处理方式。权限收得紧,注入只能读到几张无关紧要的表;权限放得开(应用直连管理员账号),注入可以越库、读写文件、在数据库服务器上执行命令,战火从数据层烧到系统层。这也是蓝队"最小权限"策略的定价依据——它不能防注入发生,但能决定注入发生后的天花板。

检测与应急

注入在日志与流量里留下指纹:含引号、注释符、数据库关键字的异常参数;突然出现的高频同形态请求(自动化扫描器特征);数据库错误日志的语法错误聚集;单请求响应时间异常(时间盲注的前兆)。一条最小检测规则:

-- WAF/日志层:疑似注入参数的聚合(命中关键字 + 引号组合) SELECT ts, path, param_snip, count(*) AS hits FROM waf_log WHERE ts > now() - interval '15 minute' AND (param_snip ~* '(union.*select|or.?''1''=|extractvalue|--|#|;)') GROUP BY ts, path, param_snip HAVING count(*) >= 3 ORDER BY hits DESC;

应急上,确认注入后第一动作是止血与取证并行:临时以虚拟补丁(WAF 精准规则或参数校验)封住注入点,导出访问与数据库日志评估被读取的范围,轮换数据库凭证(防止注入拖走的连接串被复用),再排期根治改造。跳过取证直接修复,会永远丢失"对手拿到了什么"的答案。

读完这节做个自测:把自家系统所有"接收输入并进入数据库"的入口列出来,逐个问一句——它进语句时,走的是拼接还是占位符?答案决定你是已经安全,还是只是还没被扫到。

输入点清单:注入面的盘点模板

自测说到"列出所有入口",具体怎么列?按数据流向逆推,一张表就能盘完注入面:

输入来源 典型例子 容易忽略的变体
查询参数 搜索词、筛选值、排序字段 隐藏字段、分页游标
请求体 表单、接口字段 数组下标、批量操作的 id 列表
请求头 排序语言、渠道标识 用户代理、自定义追踪头入库场景
路径段 资源标识、租户名 伪静态规则里的参数化段
二级来源 数据库里存的"上一次输入" 二阶注入:入库时无害,出库拼接时发作

最后一行值得专门强调:二阶注入是盘点时最容易漏的形态——数据入库时经过了转义,看似安全;但被读出后拼进了另一条语句,转义语境已经消失,攻击载荷原地复活。这也是为什么根治必须落在"输出到 SQL 的那一刻走参数化",而不是"输入进系统的那一刻做过滤"——入口千千万,出口只有一类。把这张表交给开发自查,比空喊"注意注入"有效得多。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U