授权与合规声明
本文全部操作对象均为自建隔离靶场(本机容器或隔离虚拟机),涉及安全测试的环节必须以取得合法授权为前提。未经授权的渗透测试违反《中华人民共和国网络安全法》与《刑法》相关条款,须承担相应法律责任。本文只讲环境配置、版本对照与靶场隔离,不含任何攻击步骤、利用载荷与绕过手法,请勿将文中环境指向任何非自有系统。
一、现象:靶场里那个 root,和它带出来的习惯
1.1 搭建笔记里最后一行的那个 root
打开任何一份靶场搭建笔记,连库那一行几乎都是 root,口令 root,或者干脆留空。这在靶场里没有问题:库是刚建的,数据是假的,跑不起来就删掉重来。root 省掉了"为什么这张表看不见""为什么这句建表被拒"的所有麻烦。
问题在于这个习惯会跟着人走。你练完一个注入点,回头给自己的小项目写配置,多半还是那一行,只把地址换成了自己的机器。等到某天想"给应用单独开个账号吧",才发觉这件事从头到尾没人讲过:不知道该给什么,不知道给多少算够,也不知道给完之后拿什么来确认。
1.2 四种听起来像同一件事的现象
初学者描述这类问题,几乎都用同一句话:“权限不对”。但这句话底下至少藏着四件事,它们卡在不同的位置,属于不同的检查阶段。
| 你看到的现象 | 大概率卡在 | 属于哪个阶段 |
|---|---|---|
| 报连不上,可用户名口令明明是对的 | 没匹配到你以为的那一行账号记录 | 第一阶段:连接验证 |
| 连上了,但看不到目标库里的表 | 库级权限没给,或者给的库名不是这个 | 第二阶段:库级 |
| 能看到表,建表被拒 | 只有读写权限,没有结构变更这一类 | 第二阶段:库级或对象级 |
| 权限全给了,还是不能给别的账号授权 | ALL本身就不含授权权 | 第二阶段:授权权 |
四种现象分布在两个阶段、三个层级上,名字却是同一个。因为表现都是"这句不行",最自然的应对就是再给多一点——把 SELECT 加成ALL,把shop.*加成*.*。这一招有时候管用,不管用的时候,你会以为是自己给得还不够。
1.3 这篇谈什么,不谈什么
这篇只谈账号本身的三件事:能连到哪(来源主机)、能对哪个库做什么(层级与动词)、能不能把权限转手发出去(授权权)。三件合起来,才是"该收到多小"的完整答案。
明确不谈的有四样:连接数上限、导出导入、字符集、localhost 与 127.0.0.1 怎么选路。它们都会让人看到"连不上",但不是权限问题,把它们和权限混在一起谈,只会让判断更乱。
也不谈绕过。这篇从头到尾只回答一个问题:一个应用账号该收到多小,收完怎么核。任何"怎么多拿一点"的内容都不在范围内。
环境说明先说在前面:截至 2026-10-24,写这篇用的机器上没有安装mysql与mysqld。所以下面每个 SQL 块都是没跑过的,全部标了待验证。能确认的只有"没装"这一件事。
二、权限不是一个开关,是按上下文分层的
2.1 官方讲权限,第一件事是分层
手册讲权限体系,第一件事不是列清单,是分层。它对管理类权限的说法是:这些权限是全局的,因为它们并不针对某一个特定的库。对库级权限的说法是:作用于一个库,以及库里所有的对象。对象级还更细一档,可以给库里某个具体对象、某类对象,也可以给所有库里的所有同类对象。
三句话合起来只有一句:同一个动词,写在不同的ON后面,管到的范围完全不同。权限不是"有没有"的问题,是"在哪一层有"的问题。这一句不先立住,后面所有讨论都会滑回"给多一点"。
| 层级 | 写法 | 管到哪里 | 典型内容 |
|---|---|---|---|
| 全局 | ON *.* | 整个实例 | 建库、管账号、管复制、关服务 |
| 库级 | ON shop.* | 一个库及其内部所有对象 | shop 下所有表的读写 |
| 表级 | ON shop.orders | 一张表 | 只读写这一张 |
| 列级 | ON shop.orders (列名) | 表里的某几列 | 只让看某几列 |
2.2ON *.*与ON shop.*是两个世界
这两行只差三个字符,权限面差一个量级。
差在哪儿:全局层有一批"没有库可挂"的权限。建库、建账号、管复制、关服务,这些动作的对象是整个实例,所以只能挂在*.*上;挂到任何ON shop.*上都是语法错误,因为它们不属于那一层。
反过来,ON shop.*给满了也不会溢出。你在 shop 里能建表能删表,不代表能读mysql库,不代表能建第二个库,也不代表能碰任何实例级的东西。
一个很常见的误解,是把"库级 ALL"读成"这个库上的最高权限,那差不多就是 root 了"。不是。库级 ALL 是"这个库上能有的全部",而 root 站的位置在它上面一整层——差的不是"多一点",是"另一类"。这在排障时很关键:如果你的问题是"看不到另一个库",那么在shop.*上怎么加都不可能解决,因为你要的东西不在这一层。
所以看到ON *.*就当成红灯。教程里写它,是因为教程的目标是"让环境跑起来";你写自己的配置时,它绝大多数情况是多余的。
2.3 第一个误判:把"语法能给"当成"该给"
因为ALL在每一层都合法,语法上你几乎总能写出一句能执行的 GRANT。执行成功会给人一个很强的错觉:这件事做对了。
但 GRANT 只检查"你有没有资格做这个授权动作",不检查"这个授权合不合理"。它不问你为什么给全局权限,不问你为什么把来源写成%,也不问你为什么给一个只读页面配了删除权限。语法通过和授权合理,是两件完全不相干的事。
三、三个被读错的词
3.1ALL:按级别封顶,且不含授权权
ALL是被误读最多的一个词。它读起来像"全部",直觉上等于最高。手册的定义是:这类写法是"某一权限级别上所有可用权限"的简写,但不包括GRANT OPTION。
这半句话里有两层限制。
第一层,按级别封顶。ALL给的是"这一层能有的全部",不是"这个实例能有的全部"。GRANT ALL ON shop.*不会顺带给你建库的权限,也不会顺带给你管账号的权限。级别是天花板,它只是顶到天花板为止。
第二层,不含GRANT OPTION。就算写的是GRANT ALL ON *.*,拿到的是全局层所有权限,但仍然不能发给别人。原文括号里那个 except 就是为这件事写的。
所以"我给了 ALL,还是不能给别的账号授权"不是配置出了错,是ALL的定义里就写着这一条。很多人在这里怀疑是版本差异或者语法问题,实际是没读完那半句。
3.2GRANT OPTION:不是权限,是转发权
手册对它的定义是:使自己可以把自身拥有的那些权限授予别人,或者从别人那里收回。
注意那个从句:“那些你自己拥有的”。它不能凭空造权,只能把手里已有的再分一份。所以在权限模型里它不算一种"能力",算一种"传递关系"——它决定这张权限网能不能继续往外长。
应用账号要不要?判断口径很干脆:运行期有没有一条语句是 GRANT 或者 REVOKE?没有,就不给。
给了它,等于把账号的权限面从"能读写这些表"扩到"能重新安排谁能读写哪些表"。更要紧的是,一旦它被用上,你在别处做的收权会被绕回去:A 收干净了,B 还能把权限发回给 A。
3.3USAGE:它是"没有权限",不是"授权失败"
USAGE的定义只有一句:它代表"没有权限"。
它是占位符:账号存在,能过第一阶段,但在这一层没有任何权限。作用是让"该层为空"有可写的形式,而不是留白。
所以SHOW GRANTS里出现USAGE ON *.*非常正常。很多人看到就以为"没给上",然后继续加,一路加到ALL ON *.*才"好使"——其实真正要给的那条一直在下面,只是被第一行挡住了视线。而加到最后,恰好就是 2.2 里说的那盏红灯。
⚠️代码待验证
SHOWGRANTSFOR'app'@'10.0.0.5';读这份输出分三步。先数行数,一行对应一个层级的一次授权,行多通常代表历史遗留多。再看每行是哪个层级:ON *.*是全局,ON shop.*是库,ON shop.orders是表——层级比动词更该先看,因为它决定这一行的影响面。最后才看动词,拿去和 5.1 的清单对一遍。
USAGE行不是错误提示,是"该层为空"的显式写法。判断有没有权限,要看完整输出,不要停在第一行。
四、真正让权限"不对"的,是匹配到的那一行
4.1 两阶段:先决定能不能连,再决定能做什么
手册把访问控制分成两个阶段。第一阶段只验一件事:这条连接能不能建立,判据是身份、凭据,以及账号有没有被锁——那条记录里表示锁定的字段必须是 N。第二阶段才逐条语句去验"这一句能不能跑"。
最关键的后果是:身份在连接建立的那一刻就钉死了。这条连接之后发的每一条语句,都按那个身份查权限,不会重新认一次人。
这句话能解释两类看着很像、其实不同的问题。“我用 app 连的,怎么像是在用另一个账号”——这发生在第一阶段,是匹配到了别的行。“我改了权限,这条连接还是不行”——这发生在第二阶段,和变更对连接的作用时机有关。两类入口完全不同,混着看就只能反复重启服务碰运气。
4.2 身份不是用户名,是用户名加来源主机
第一阶段认的身份,是User和Host两列共同决定的。手册写得很直白:这两列一起决定匹配到哪一行。
同一个用户名可以有多行,对应不同来源。难的是多行都匹配时用哪一行。规则是:服务器按排好序的顺序往下扫,用第一个匹配上的行。排序把最具体的 Host 放前面,%最不具体。手册还给了主机部分的具体度:从 8.0.23 起,IP 字面量比 CIDR 具体,CIDR 比带掩码的具体;%表示任意主机、最不具体;空字符串也表示任意主机,但排在%之后。
| 行 | User | Host | 谁能匹配上 | 具体度 |
|---|---|---|---|---|
| 1 | app | 10.0.0.5 | 只有这一个地址 | 最具体 |
| 2 | app | 10.0.0.0/255.255.255.0 | 这一个网段 | 中间 |
| 3 | app | % | 任意来源 | 最不具体 |
这张表的用法不是背排序,是意识到一件事:你写的行和实际生效的行,可能不是同一行。只要同一个用户名下面有多行,这个可能就存在。而一旦存在,"加权限了怎么没用"就不需要靠猜了。
4.3 空用户名先匹配:官方点名的那个误解
到这里可以说本文的主线了。
手册对空用户名的说明大意是:User这一列为空,能匹配任何用户名;而且一旦匹配上,这条连接后续所有的访问检查都用这个空用户名来做。
官方在同一节里直接点出,这是一个常见的误解——很多人以为"连接时显式写了用户名,服务器就会先去找那一行"。不是。排序在前的行先赢,而空用户名的行能匹配任何用户名,所以它排在你那一行前面的时候,你以为的那个账号压根没被查过。
后果很隐蔽:你以为在用 app 的权限,实际在用匿名行的。给 app 加权限加多少都不生效,因为没走到那一行。于是出现一类特别抓狂的现象——SHOW GRANTS FOR 'app'@...的输出干净合理,应用跑起来还是不行。你会怀疑缓存、怀疑连接池、怀疑配置没生效,唯独不怀疑"你查的那个账号不是正在用的那个"。
还有一个容易漏的细节:一旦匹配,整条连接都用这个身份,不是每条语句重新挑一行。
判据只有一个,手册也写了:CURRENT_USER()返回user_name@host_name形式的值,取自实际匹配到的那一行记录。
⚠️代码待验证
SELECTCURRENT_USER();排障第一条命令就是它。别拿连接串里写的用户名当答案——那个是"我想要是谁",不是"我实际是谁"。两者的差值,就是这类问题的全部空间。
⚠️代码待验证
SELECTUser,HostFROMmysql.userWHEREUser='';这条用来找匿名行,需要读mysql库的权限,只在你自己有管理权限的靶场里跑。目的只有一个:看清楚有没有这样一行、它的 Host 是什么、排在哪。这一步是"看清楚",不是"绕过去"。
五、收到多小:先定库,再定动词,最后定来源
5.1 从"这个应用会发什么语句"往下减
收权的方向不是"从最小往上加",是"从实际会发的语句往下减"。差别在心态:往上加的人永远在担心"加少了会不会出问题";往下减的人手里有一份清单,减到刚好为止就停。
第一步不是开账号,是把应用运行期会发哪些语句类型列出来。绝大多数业务应用落在这三档:只读的只有查询;一般业务是查询加增删改;需要改结构的再加建表、改表、删表、建索引。
第三档单独说。改结构一般只发生在部署和迁移的时候,不在运行期。更稳的做法是给迁移单独开一个账号,只在部署时用;业务账号永远不碰结构变更。这样一次误操作最多写坏一行数据,而不是把整张表连结构一起覆盖掉。
需要说明:这三档是我的工程建议,不是手册原文。手册给的是分层这套定义,没有给"一个应用该给几个权限"的清单,因为那取决于应用本身,不存在通用答案。这点要说清,免得被当官方口径引用。把三档落成表,加一列给错的后果:
| 权限项 | 运行期要不要 | 给错的后果 |
|---|---|---|
| 目标库的查询权限 | 看应用读不读 | 读到了本不该读的表 |
| 目标库的增、删、改权限 | 看应用写不写 | 写坏或删掉数据 |
| 目标库的建表、改表、删表 | 一般不要,交给迁移账号 | 一次误操作能清掉整张表 |
| 全局层那一类管理权限 | 不要 | 影响面从"这个库"扩到"整个实例" |
GRANT OPTION | 不要 | 能把手里权限再发出去 |
mysql库的读权限 | 不要 | 见 5.2 |
来源主机写成% | 尽量收窄 | 凭据一旦外流,可用范围没有边界 |
5.2mysql库不给普通账号读
手册里有一条很直接的安全提醒:不要给非管理用户读mysql系统库里那些表的权限。
这是本文引用的原文里,唯一一条直接点名"某个库不该给"的话,值得单独一节。
原因是那个库存的不是业务数据,是账号和权限本身——有哪些账号、各自从哪些主机来、各自有什么。读到它,等于把这张图整份交出去。对防守方来说它是资产;对任何拿到这个应用账号的人来说,它同样是资产,而且是一份能省掉大量猜测的资产。
这里有个具体的踩法,正好接上 2.2:GRANT SELECT ON *.*是包含mysql库的。你本来只想让应用读所有业务库,一句*.*就把系统库一起带了进去,而且带得毫无声息——它不会提醒你,也不会在SHOW GRANTS里单独列一行。
5.3 空密码不等于任意密码
这一条几乎每个人第一次看到都会理解反。
手册的原文大意是:authentication_string这一列可以是空的。这不是通配符,也不代表任何密码都能匹配。它的意思是,这个用户必须在不指定密码的情况下连接。
拆成三点。它不是通配符:拿任意一个密码去连不会成功,所以"空密码等于任意密码"这个说法是错的。它要求的是"不带密码":能被用上的条件不是"随便填都能过",是"根本不需要填"。所以它的风险不在"猜得中",在"没有这道门槛"——前者靠口令强度和限速缓解,后者没有强度可言,也没有猜的过程。
推论很直接:一个空密码账号,如果 Host 是%,那么任何能连到这个端口的人都不需要凭据。这跟"口令弱"不是一个量级——它压根不是弱,是没有。
一句校准:不要因为它"不是任意密码"就觉得它安全,也不要把它写成"任意密码都能进"。两头都错,一个会漏掉风险,一个会让你排障时找错方向。
配套资料获取:这一章的判断表和"应用类型对应只留哪几个动词"的对照页,我并成了一张单页,和正文两张表逐行对应:
六、自查:把你手上的账号过一遍
6.1 三步,顺序不能换
第一步,确认匹配到哪一行。SELECT CURRENT_USER();拿回的就是user@host,这是判据,也是后面所有判断的前提。没有这一步,后面两步都建在假设上。
第二步,看这一行有什么。SHOW GRANTS FOR CURRENT_USER();要用CURRENT_USER(),不要硬写用户名——硬写的话,你查的是"我以为的那个账号",而问题往往就出在"我以为"这三个字上。
第三步,对着应用真实会发的语句,把多出来的砍掉。
顺序不能换:第一步错了,后面两步都是在看别人的权限,看得再仔细也没用。而第一步出错恰恰最常见,因为它不报错。
⚠️代码待验证
SELECTCURRENT_USER();SHOWGRANTSFORCURRENT_USER();两句一起跑,输出的第一行是"实际是谁",后面几行是"这个身份有什么"。拼起来才算一次完整判断。
6.2 一张表:每一项怎么核
把前面几章收敛成一张对照表:左列是检查项,中列该不该给应用,右列怎么看出来。
| 检查项 | 该不该给应用 | 怎么核 |
|---|---|---|
| 全局层权限 | 一般不该 | SHOW GRANTS第一行应当只有USAGE |
| 目标库的查询权限 | 按需 | 对一遍应用有没有读操作 |
| 目标库的增删改权限 | 按需 | 对一遍应用有没有写操作 |
| 目标库的结构变更权限 | 一般不给 | 交给单独的迁移账号 |
GRANT OPTION | 不给 | 输出里出现它就算超标 |
mysql库的读权限 | 不给 | 看有没有*.*或mysql.* |
| 来源主机 | 收到具体 | Host是%就要问一句为什么 |
| 同一用户名的行数 | 越少越好 | 数一下User相同的行有几条 |
| 空密码账号 | 不该有 | 看authentication_string是否为空 |
6.3 三个常见误判
误判一:看到USAGE以为没给上。见 3.3,出现频率最高,因为它看起来就像"空"。
误判二:以为ALL是最高。见 3.1,最容易被"我全给了"这句话固化下来,之后所有排查都会绕开真正的原因。
误判三:改了权限,以为立刻对所有连接生效。这里必须标待验证——FLUSH PRIVILEGES到底在什么条件下需要,本批没有核到对应页面。只能给一句方向性的判断:用 GRANT、REVOKE 这类语句改的权限,一般不依赖它;只有直接去改授权表的时候才可能需要。这句本身也标待验证,不要当结论引用。
同样没核到的还有一件事:权限变更对此前建立的连接什么时候生效。这条在实践里很要命——你在一个会话里改完,另一个会话可能还是老样子,于是以为没改成功,又改一遍。稳妥做法是改完重连一次再验,这只是我的建议,不是官方结论。
七、边界,以及收权的顺序
7.1 这篇不写什么
不写连接数上限,那是资源与配置。不写导出导入,批量搬数据有自己的权限要求。不写字符集,它决定的是存进去的字节怎么解释。不写 localhost 与 127.0.0.1 怎么选路,那是连接串与解析层的事,和"这一行匹配不匹配"是两套机制。也不写任何绕过权限的做法,不写提权——收权的价值在于把面缩小,不在于找到更大的面。
whichmysql mysqld本机没有安装mysql与mysqld,所以上面所有 SQL 一行都没跑过。这条命令跑过,输出是mysql not found与mysqld not found。它证明不了别的,只证明"我确实没有执行环境",这一点得交代,否则读者会默认文中的 SQL 都跑通了。
7.2 收权顺序:先做能立刻看到效果的那一步
| 顺序 | 动作 | 为什么排在这个位置 |
|---|---|---|
| 1 | 用CURRENT_USER()确认在用的那一行 | 前面全错的话,后面都白做 |
| 2 | 清理空用户名行和用不到的重复行 | 匹配的不确定性是"给多了没用"的根源 |
| 3 | 把来源主机收到具体地址或网段 | 范围越窄,凭据外流后的可用面越小 |
| 4 | 按库收,把*.*换成本库.* | 一步砍掉跨库面和系统库 |
| 5 | 按动词收,去掉结构变更与GRANT OPTION | 砍掉"能改结构"和"能转发" |
| 6 | 空密码账号要么删、要么设口令 | 这不是强度问题,是有没有门槛 |
| 7 | 改完重连再验一次 | 变更对连接的作用时机本批未核到 |
顺序不是按投入排的,是按"错了以后返工代价"排的。第 1、2 步几乎零成本,但它们决定后面五步是不是在做无用功。第 7 步最容易被省掉,而它是唯一能确认"改了真的生效"的一步。
7.3 一句收尾判断
"该收到多小"没有统一答案,但有统一的判断方式:把这个账号在正常运行中会发的语句列出来,只给这些语句需要的权限,一个不多。列不出来的部分,就是你还不知道应用会做什么的部分,那部分先别给。
整份都列不出来,说明你还不清楚这个应用会做什么——先别开账号。清单是别人的,语句是你自己的。
配套资料获取:这一章的收权七步做成了一张可以照着勾的单页,每一步都带"做完怎么验",和上面的表一一对应:
附表 A:本文引用事实与官方出处对照表
| 编号 | 事实摘要 | 出处 | 本文位置 |
|---|---|---|---|
| DB-1 | 管理类权限是全局的,因其不针对特定库;库级权限作用于一个库及其所有对象;对象级可细到具体对象或所有库的同类对象 | MySQL 8.0 Reference Manual 8.2.2 | 2.1、2.2 |
| DB-2 | ALL是"某一权限级别上所有可用权限"的简写,且不含GRANT OPTION | 同上 8.2.2 | 3.1 |
| DB-3 | GRANT OPTION使自己可把自身拥有的权限授予或收回给其他用户 | 同上 8.2.2 | 3.2 |
| DB-4 | USAGE代表"没有权限",SHOW GRANTS用它表示该级别无权限 | 同上 8.2.2 | 3.3 |
| DB-5 | 两阶段访问控制:第一阶段只验连接能否建立,判据含账号未被锁(锁定字段须为 N);第二阶段才验语句 | 同上 8.2.6 | 4.1 |
| DB-6 | 身份由User与Host两列共同决定匹配到哪一行 | 同上 8.2.6 | 4.2 |
| DB-7 | 服务器按排序后的顺序扫描,使用第一个匹配的行;最具体的 Host 在前,%最不具体 | 同上 8.2.6 | 4.2、4.3 |
| DB-8 | User为空可匹配任何用户名;匹配后整条连接沿用该空用户名;官方点明"显式写了用户名就先匹配该用户"是常见误解 | 同上 8.2.6 | 4.3 |
| DB-9 | CURRENT_USER()返回user_name@host_name,取自实际匹配到的那一行记录 | 同上 8.2.6 | 4.3、6.1 |
| DB-10 | 不要给非管理用户读mysql系统库中表的权限 | 同上 8.2.6 | 5.2 |
| DB-11 | authentication_string可为空,但不是通配符、不代表任何密码可匹配,而是要求不指定密码连接 | 同上 8.2.6 | 5.3 |
| DB-12 | 主机部分具体度(8.0.23 起):IP 字面量 > CIDR > 带掩码;%为任意主机且最不具体;空串亦为任意主机但排在%之后 | 同上 8.2.6 | 4.2 |
| DB-13 | 待验证:FLUSH PRIVILEGES的触发条件、权限变更对已有连接何时生效 | 本批未核到对应页面 | 6.3、7.2 |
| DB-14 | 本机未安装mysql与mysqld,全文 SQL 块均为未执行 | 本机实测 2026-10-24 | 1.3、7.1 及全文 SQL 块 |
附表 B:术语速查表
| 术语 | 一句话说明 |
|---|---|
| 全局权限 | 挂在ON *.*上、作用于整个实例、不针对某个库的那一类 |
| 库级权限 | 挂在ON 库名.*上,作用于这个库及其内部所有对象 |
ALL | “该级别能有的全部”,按级别封顶,且不含授权权 |
GRANT OPTION | 转授权,能把自己手里的权限再发给别人,不是一种权限 |
USAGE | 代表"没有权限"的占位写法,出现在SHOW GRANTS里属正常 |
SHOW GRANTS | 按层级逐行列出某账号持有权限的语句,一行一层 |
CURRENT_USER() | 返回实际匹配到的用户名@主机,是判断"我是谁"的判据 |
| 匿名账号 | User列为空的账号记录,可匹配任何用户名 |
| 来源主机 | Host列,与用户名共同构成身份,也决定匹配的先后 |
写在最后:这篇用到的资料
写这篇文章时,我把"权限不对"拆成两个阶段三个层级重新对了一遍,发现真正难的不是给多少,而是确认自己到底在用哪一行,顺手也整理了几份配套的东西:
- 靶场环境对照表:DVWA、upload-labs 在 Windows / macOS / Linux 三平台的可行性与推荐路径
- Web 安全学习路线图:从基础打牢到安全管理,四个阶段各学什么
- 常用靶场清单:每个靶场练什么、适合哪个阶段
资料是我自己整理的,放在下面这个码上,扫码即可获取:
添加时备注「靶场」,优先通过。
拿到之后建议先看环境对照表那一份,把「我这个应用账号到底连到了哪一行」先对着自己的靶场查一遍,比记权限清单更管用。