医院电子病历越权与下载攻防全解析:从水平越权、垂直提权到JWT防线与目录穿越
2026/8/9 4:36:18 网站建设 项目流程

目录

1. 水平越权:改一个数字看遍全院病历

1.1. 越权是怎么发生的:链接里的那段数字

1.2. 篡改 ID:把 19 改成 18

1.3. 为什么叫"水平":只能看到同权限的

2. 垂直越权:把角色字段改成 admin

2.1. 登录抓包:发现请求里的角色字段

2.2. 篡改角色:把 user 改成 admin

2.3. 水平越权与垂直越权的区别

3. 越权的本质:一个不可调和的悖论

3.1. 悖论的两端:必须发 ID,又怕被改 ID

3.2. 悖论的核心:身份凭据不可信

4. JWT 防线:给身份加一道不可篡改的签名

4.1. 破解悖论:让身份信息"改不动"

4.2. JWT 的三段结构:签名就是护城河

4.3. 为什么无法篡改:没有密钥就签不了名

5. 目录穿越:下载文件时偷走系统文件

5.1. 下载功能的文件名参数

5.2. 把文件名换成系统文件

6. 第一轮修复:过滤特殊字符与限定目录

6.1. 过滤掉特殊字符

6.2. 限制只能访问网站内部目录

7. 第二轮博弈:文件名规律照样能枚举

7.1. 发现文件命名规律

7.2. 替换工号:依旧能下载他人文件

7.3. 终极修复:随机字符串重命名

8. 攻防演进全景总结

9. 写在最后


前言

这篇文章的灵感来源于一次安全测试的复盘。

当时评估一套医院电子病历系统,本来只想验证自己的就诊记录能不能正常读取。

结果发现,只要改一下链接里的数字,就能看到别的患者的病历。

顺着这条线一路挖下去,从水平越权、垂直提权,到 JWT 防线、目录穿越,最后落到文件名枚举,把一套完整的越权与文件下载攻防链路完整走了一遍。

这篇文章就是这次评估的完整记录。

文中所有代码仅用于安全研究与防御学习,严禁用于任何非法用途。

个人主页:艺杯羹

1. 水平越权:改一个数字看遍全院病历

1.1. 越权是怎么发生的:链接里的那段数字

登录医院电子病历系统,点击查看自己的就诊记录。

页面正常加载,屏幕上出现了自己的检查和用药信息。

但细心观察会发现,当前病历所在网页的链接里,藏着一串数字。

这串数字不是别的,正是当前患者的 ID。

1.2. 篡改 ID:把 19 改成 18

既然链接里带着 ID,那把这个数字改掉会怎样?

把 19 改成 18,回车,屏幕上瞬间出现了另一位患者的病历。

图片说明:患者 ID 直接暴露在链接中,篡改 ID 即可读取其他同权限患者的病历。

再改成 17、16、15……一路点下去,几乎能翻遍整家医院所有普通患者的就诊记录。

这种攻击,就叫做水平越权

1.3. 为什么叫"水平":只能看到同权限的

水平越权之所以叫"水平",是因为它只能横向触达与自己权限相同的数据。

普通患者只能看到其他普通患者的病历。

而权限更高的人,比如管理员、科室主任,普通患者是看不到的。

对比项

可见范围

正常访问

仅本人病历

水平越权

所有同权限患者的病历

越权边界

无法触达更高权限的数据

也就是说,水平越权只能横向扩散,无法向上突破。


2. 垂直越权:把角色字段改成 admin

2.1. 登录抓包:发现请求里的角色字段

水平越权只能看普通患者,权限更高的数据依旧无法触及。

于是回到登录界面,重新输入用户名和密码。

在点击登录的瞬间抓包,发现登录请求里不仅带着用户名和密码。

还额外携带了一个角色字段role=user

2.2. 篡改角色:把 user 改成 admin

把请求里的user改成admin,重新发包。

登录成功后,系统管理员的管理界面直接出现在眼前。

这一次,连权限最高的数据也尽收眼底。

图片说明:登录请求中的角色字段被篡改,从普通用户提权为管理员,突破权限等级边界。

这种从低权限直接提升到高权限的攻击,就叫做垂直越权

2.3. 水平越权与垂直越权的区别

对比维度

水平越权

垂直越权

攻击方向

横向(同级)

纵向(跨级)

篡改对象

数据 ID

角色 / 权限字段

触达范围

同权限数据

更高权限数据

危害程度

水平越权是"看别人",垂直越权是"当领导"。

一次是横向扩散,一次是纵向突破。


3. 越权的本质:一个不可调和的悖论

两种越权攻击看似手法不同,背后却指向同一个根因。

这个根因,本质上是一个不可调和的悖论

3.1. 悖论的两端:必须发 ID,又怕被改 ID

一方面,用户在执行操作时,必须把自己的 ID 发送给服务器。

服务器需要在后台解析用户身份,才能据此授予对应的操作权限。

另一方面,一旦用户有权限把自己的 ID 发给服务器,他就能把这个 ID 篡改成别人的。

发送 ID 与篡改 ID,在同一个请求里完成了闭环。

3.2. 悖论的核心:身份凭据不可信

悖论面向

具体表现

必须发 ID

服务器需要身份才能鉴权

能发就能改

发出去的 ID 可能被篡改

结果

越权漏洞天然存在

问题不在用户,而在"用户提交的身份信息本身不可信"。

只要服务器完全信任客户端提交的身份字段,越权就永远防不住。


4. JWT 防线:给身份加一道不可篡改的签名

4.1. 破解悖论:让身份信息"改不动"

要解决悖论,关键在于让用户没法篡改身份信息。

登录时,服务器不再依赖客户端提交的角色字段。

而是直接根据用户名,在后台数据库自动匹配用户角色。

匹配成功后,服务端把用户 ID、用户角色等信息签名成一个 token 返回给用户。

用户之后的每一次请求,都必须携带这个 token。

4.2. JWT 的三段结构:签名就是护城河

这个 token 就是JWT(JSON Web Token),目前最常见的防越权手段之一。

JWT 由三段组成,中间用点号分隔:

Header.Payload.Signature

JWT 分段

内容

作用

Header

算法类型(如 HS256)

声明签名算法

Payload

用户 ID、角色、过期时间

承载用户身份信息

Signature

用密钥对前两段签名

防篡改核心

图片说明:用户 ID 与角色被签名进 JWT,缺少服务端密钥的用户无法篡改身份信息。

4.3. 为什么无法篡改:没有密钥就签不了名

由于 JWT 是加密的,且用户无法获得对应的密钥。

所以用户一旦篡改了用户 ID 或角色,签名就会立刻失效。

服务端校验签名失败,直接拒绝请求。

import hmac import hashlib import base64 SECRET = "server-side-secret-key" # 密钥只保存在服务端 def verify_token(token: str) -> bool: """ 校验 JWT 签名是否有效 任何对 Header/Payload 的篡改都会导致签名不匹配 """ header, payload, signature = token.split(".") expected = base64.urlsafe_b64encode( hmac.new(SECRET.encode(), f"{header}.{payload}".encode(), hashlib.sha256).digest() ).decode().rstrip("=") return hmac.compare_digest(expected, signature) # 篡改 Payload 里的角色后,签名必然校验失败 token = "header.eyJyb2xlIjoiYWRtaW4ifQ.fake-signature" print("签名有效" if verify_token(token) else "篡改被拒绝")

用户改不了 ID,也改不了角色,越权这条路被从根上堵死。


5. 目录穿越:下载文件时偷走系统文件

JWT 守住了身份这一关,但攻击者并没有放弃。

在医院系统里,还有一个功能引起了注意:下载病历报告

5.1. 下载功能的文件名参数

每次点击下载按钮,浏览器都会跳出一个新的链接。

链接的末尾往往跟着要下载的文件名。

比如:download?file=202608_10019_report.pdf

5.2. 把文件名换成系统文件

看到文件名跟着链接走,攻击者灵机一动。

如果把文件名替换成系统文件会怎么样?

无论是在 Windows 还是 Linux 系统里,都有一些系统文件存在于固定路径下。

比如/etc目录下的passwd文件,保存着 Linux 系统的用户密码信息。

把链接里的文件名直接替换成/etc/passwd,回车。

图片说明:文件名参数未校验,被替换为系统路径后,直接下载了服务器上最敏感的隐私文件。

系统里重要的隐私文件,就这样被下载了下来。

这种通过文件名穿越目录、访问任意文件的技术,就叫做目录穿越


6. 第一轮修复:过滤特殊字符与限定目录

工程师紧急修复了这个漏洞。

6.1. 过滤掉特殊字符

第一招,不允许文件名里传递斜杠、点之类的特殊字符。

/\..这些路径穿越的"武器"统统被拦截。

6.2. 限制只能访问网站内部目录

第二招,限制网站应用只能访问网站内部的文件和目录。

即使构造出路径,也无法跳出应用目录去读取系统文件。

修复手段

说明

过滤特殊字符

拦截/\..

限定访问目录

只能读取应用内部文件

校验文件类型

只允许指定扩展名

但攻击者立刻发现,这套修复存在一个漏洞。


7. 第二轮博弈:文件名规律照样能枚举

7.1. 发现文件命名规律

攻击者注意到,病历报告的文件名存在明显规律。

文件名由**月份 + 工号(患者 ID)**组成。

比如202608_10019_report.pdf,中间的10019就是患者 ID。

7.2. 替换工号:依旧能下载他人文件

既然文件名有规律,那只需要把自己的工号替换成别人的工号。

依旧可以下载获得这个文件。

图片说明:过滤特殊字符后,攻击者又利用"月份+工号"的命名规律枚举下载他人文件;最终随机重命名才彻底封堵。

目录穿越虽然被堵住了,但文件却依然能被"猜"出来。

工程师这下没招了。

7.3. 终极修复:随机字符串重命名

工程师不得不使用系统生成的随机字符串来重新命名文件。

文件名变成了没有规律的一串乱码,无法再被推测。

这样一来,攻击者再也无法获得其他人的病历文件了。

防御阶段

文件命名

能否枚举

修复前

月份 + 工号

能,规律明显

修复后

随机字符串

不能,不可预测


8. 攻防演进全景总结

把整条链路串起来,可以看到一个清晰的攻防演进过程:

回合

攻击手段

防御手段

攻防结果

第 1 回合

修改 ID 水平越权

服务端校验身份

攻击者转向提权

第 2 回合

篡改角色垂直提权

JWT 签名防线

身份越权被杜绝

第 3 回合

目录穿越下载文件

过滤特殊字符 + 限定目录

系统文件被隔离

第 4 回合

文件名规律枚举

随机字符串重命名

文件无法再枚举

图片说明:从水平越权、垂直提权到 JWT 防线,再到目录穿越与文件名枚举,攻防双方围绕身份可信与文件可猜持续博弈。

每一次防御升级,都逼迫攻击者另寻出路。

而每一次攻击进化,又催生出更严密的防御。

这就是安全领域最核心的规律:攻防永远是一场没有终点的螺旋博弈


9. 写在最后

本文用医院电子病历系统这个场景,完整推演了从水平越权到文件名枚举的越权与下载攻防链路。

这些知识点并不局限于医疗系统,它们广泛存在于电商、金融、政务、企业内网等各类业务系统中。

理解越权的本质,才能真正构建起有效的身份鉴权体系。

对开发者而言,服务端鉴权、JWT 签名、文件名白名单与随机化,是企业级应用的基本功。

对使用者而言,越权访问他人数据属于违法行为,依据《中华人民共和国刑法》第二百八十五条,可能构成非法侵入计算机信息系统罪或侵犯公民个人信息罪。

技术本身没有善恶,但越界使用技术有代价。

希望这篇文章能提供一个清晰的越权攻防思维框架

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询