1. 赛题复盘与核心思路拆解
2022年的那场国赛,现在回想起来依然觉得肾上腺素飙升。作为中职组网络安全赛项的参与者,我独自一人面对那套涵盖Web渗透、系统加固、应急响应、密码学等多维度的综合试题,整个过程就像在迷宫里打着手电筒找出口,每一步都充满了未知和挑战。今天,我想抛开官方题解和标准答案,纯粹从一个“单兵作战”选手的视角,复盘我当时面对第六道综合题时的真实思考路径、技术抉择以及那些“踩坑”后总结出的野路子。这道题通常不是最难的,但绝对是检验选手基本功和临场应变能力的试金石,它往往伪装成一个简单的业务场景,内里却布满了陷阱。
这道题的核心场景,我记得是一个模拟的“企业内部文档管理系统”。页面看起来人畜无害,功能也简单,就是上传、查看、管理文档。但题干里那句“管理员发现系统存在异常访问,请协助排查并加固”的提示,就像侦探小说开头的那句“这是一个平静的夜晚……”,意味着平静之下暗流涌动。我的目标很明确:第一,找出存在的安全漏洞(攻击面);第二,利用漏洞获取关键信息(Flag);第三,提出有效的加固建议。这实际上是一个“攻击-取证-防御”三位一体的综合任务,非常考验选手对漏洞原理的理解深度和横向串联能力。
2. 攻击面分析与初步侦察
面对一个未知系统,莽撞地直接上工具扫描是最低效的,甚至可能触发防护机制被“封IP”。我的习惯是,先用人眼和浏览器开发者工具,像用户一样把系统摸一遍。
2.1 前端信息搜集与功能点枚举
第一步永远是手动点击。我依次访问了首页、登录页、注册页、文档列表页、上传页面和个人中心。在这个过程中,我高度关注以下几点:
- 页面参数:URL中的
id、page、file等参数,尝试修改它们。比如把?id=1改成?id=2或者?id=1',观察页面回显差异。这道题里,在查看文档详情页面,URL形如/view.php?id=5,修改id值确实能访问到不同文档,初步判断存在数据库交互。 - 隐藏内容与注释:右键查看页面源代码是基本操作。我仔细搜索了HTML注释(``),有时开发人员会在这里留下测试账号、后端接口路径甚至是一些提示。虽然这次没在注释里找到“宝藏”,但这个习惯不能丢。
- JavaScript文件分析:按F12打开“网络(Network)”标签,刷新页面,查看加载了哪些JS文件。特别关注那些看似处理用户输入、发起AJAX请求的JS代码。有时客户端校验逻辑会在这里泄露服务器端预期的数据格式或接口地址。
- Cookie与本地存储:检查
Application标签下的Cookies和Local Storage。赛题中常设置一些看似无关的Cookie值,可能经过编码(Base64、Hex等),解码后或许有意外发现。这次我发现了一个名为session的Cookie,其值是一串很长的、看似随机的字符,这通常是会话标识符,暂时不动它,但记下来。
注意:在CTF或竞赛环境中,频繁的非法请求可能导致IP被临时屏蔽。手动测试时,动作要“轻”,两次测试之间间隔几秒,并随时准备更换浏览器或使用Burp Suite的“拦截(Intercept)”功能暂停请求,避免误操作。
2.2 目录与敏感文件探测
手动浏览完表面功能后,就需要借助一些字典进行深度探测。我习惯用dirsearch或者gobuster,但在比赛紧张时,一个精心准备的自定义小字典往往更高效。我的字典里通常包含以下内容:
- 常见后台路径:
/admin,/manage,/backend,/wp-admin。 - 常见敏感文件:
/robots.txt,/.git/,/.svn/,/.env,/phpinfo.php,/backup.zip,/www.zip。 - 接口或配置文件:
/api/,/config.php,/database.php。
这次运行扫描后,有两个关键发现:
/robots.txt:访问它,发现里面有一条Disallow: /backup/。这简直是明示!立刻访问/backup/目录,果然列出了一个名为www_20220910.zip的压缩包。下载它,这是源码备份。/index.php.bak:扫描还发现了首页的备份文件。下载下来,可以与当前运行的index.php做对比,有时能发现被修复的漏洞点或隐藏的逻辑。
获取到源码,战局就明朗了一半。这步操作的核心思路是:寻找开发或运维人员无意中遗留在生产环境的“开发痕迹”,这些痕迹是通往系统内部最直接的捷径。
3. 源码审计与漏洞定位
拿到www_20220910.zip后,我迅速在本地环境解压,并用像VS Code这样的编辑器全局搜索关键函数和危险操作。
3.1 上传功能审计(漏洞点一)
我直奔upload.php文件。文件上传是永恒的漏洞富矿。我快速浏览代码,寻找几个关键点:
- 文件类型校验:是检查
Content-Type($_FILES['file']['type'])还是文件扩展名(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION))?题目中,代码仅用$_FILES['file']['type'] == 'image/jpeg'来判断是否为图片,这是一个经典漏洞。Content-Type在HTTP请求头中,完全由客户端控制,可以被Burp Suite轻易篡改。 - 文件内容校验:有没有使用
getimagesize()函数进行真正的图片内容验证?这段代码没有。 - 存储路径与文件名:上传后的文件被重命名了吗?还是保留了原始文件名?代码显示,文件被移动到
uploads/目录下,但文件名采用了md5(时间戳+原文件名) + '.jpg'的方式。这意味着即使我上传了一个.php文件,只要绕过前端校验,服务器端也会以.jpg后缀保存,无法直接解析执行。
但是,这里存在一个致命的逻辑缺陷。我继续审计与上传文件相关的“查看”功能。在view.php中,我发现文件路径是通过id参数从数据库查询得到的。追踪数据库查询,发现文件路径存储在数据库中,而文件后缀名也一并存了进去。那么,如果我在上传时,通过Burp Suite将Content-Type改为image/jpeg,但文件名保持shell.php.jpg,服务器端校验通过,存入数据库的路径可能就是uploads/md5值.php.jpg。关键在于,服务器(如Apache)如何解析这个文件?如果配置了AddType application/x-httpd-php .php,那么.php.jpg不会被当作PHP执行。但常见的疏忽是,Apache的mod_negotiation模块开启时,对于shell.php.jpg这样的文件,如果请求shell.php,服务器可能会返回shell.php.jpg的内容,但这通常也不执行PHP。
真正的突破口不在这里。我继续搜索“文件包含”函数。很快,在download.php中发现了宝藏:include($_GET['template'] . '.php');。这里存在一个本地文件包含(LFI)漏洞。参数template用户可控,且直接拼接.php后传入include。这意味着我可以控制包含的PHP文件路径。
3.2 文件包含漏洞利用(攻击链串联)
现在,攻击链清晰了:
- 利用不严谨的上传校验,上传一个内容为PHP代码的图片文件(例如,在GIF文件头部添加 ``),但保存后的后缀是
.jpg。 - 利用文件包含漏洞,通过
download.php?template=参数,去包含我上传的这张“图片”。因为include函数会执行被包含文件中的PHP代码,无论其后缀名是什么。
具体操作如下:
- 制作一个Webshell图片:
copy normal.jpg /b + shell.php /b webshell.jpg(Windows),或者使用exiftool将PHP代码写入图片的EXIF信息:exiftool -Comment='' webshell.jpg。我选择了后者,因为更隐蔽。 - 使用Burp Suite拦截上传请求,将
Content-Type修改为image/jpeg,然后放行。上传成功,得到文件路径,例如uploads/a1b2c3d4e5f6.jpg。 - 访问
download.php?template=../uploads/a1b2c3d4e5f6。这里../用于目录穿越,定位到上传目录。服务器会执行include('../uploads/a1b2c3d4e5f6.php');,由于我们上传的文件实际是.jpg,但include会尝试包含a1b2c3d4e5f6.php,这个文件不存在。所以需要利用PHP的封装协议。 - 更优雅的利用:PHP封装协议。PHP的
php://filter协议可以用于读取文件源码,zip://可以用于解压包含shell的压缩包。但这里我们上传的是图片,直接包含无法执行。这时,另一个协议phar://可以帮我们反序列化,但构造复杂。最直接的是,我注意到上传的文件服务器能访问。那么,我可以尝试利用include包含远程文件(RFI),但比赛环境通常禁用了allow_url_include。所以,我回到LFI的经典利用:日志文件包含。
3.3 日志注入与GetShell
如果服务器开启了错误日志,并且我知道日志文件的路径(常见如/var/log/apache2/access.log),我就可以通过User-Agent或HTTP请求参数注入PHP代码,然后通过LFI漏洞包含这个日志文件,从而执行代码。
步骤:
- 探测日志路径:利用已有的LFI,尝试包含一些常见路径,如
../../../../var/log/apache2/access.log。通过download.php?template=../../../../var/log/apache2/access_log%00(如果PHP版本允许空字节截断)或者直接尝试。我通过返回的页面内容(大量HTTP访问记录)确认了日志路径正确。 - 注入PHP代码到日志:使用Burp Suite或curl,发送一个特殊的HTTP请求,在User-Agent字段中携带PHP代码。
curl -A "" http://target-site.com/ - 包含日志文件执行代码:访问
download.php?template=../../../../var/log/apache2/access.log。服务器会执行日志文件中我们注入的PHP代码。为了精确执行,我需要找到我刚刚注入的那条日志记录。通常,我可以先注入一个简单的来测试,如果成功,页面上会显示 `phpinfo` 的信息。确认成功后,再注入Webshell代码,例如。
实操心得:日志文件通常很大,包含整个文件可能会导致PHP内存超限或执行超时。一个技巧是,在注入代码后,立即发送大量其他请求,将自己的恶意日志记录“顶”到日志文件末尾附近,然后包含日志文件时,可以尝试只包含最后几行,例如使用
tail命令的思路,但在LFI中不易实现。更稳妥的方法是,注入一个能写入独立Webshell文件的代码,比如 ``。这样,下次直接访问/uploads/shell.php即可,更稳定。
通过日志注入,我最终在服务器上建立了一个稳定的Webshell连接,获得了系统命令执行权限。接下来就是寻找Flag。
4. 权限提升与信息搜集
获得Webshell(通常是www-data用户权限)后,真正的挑战才开始。Flag往往不在Web目录下,或者需要更高权限才能读取。
4.1 系统内部信息搜集
我首先执行了一些基础命令,了解环境:
whoami/id:查看当前用户和权限。uname -a:查看系统内核版本。cat /etc/passwd:查看系统用户。ps aux:查看运行进程,寻找以root身份运行的服务或可疑进程。sudo -l:非常关键!查看当前用户可以使用sudo以root身份执行哪些命令。如果配置不当,可能直接找到提权路径。这道题中,sudo -l返回(ALL) NOPASSWD: /usr/bin/find。这是一个经典的SUID提权点!find / -perm -4000 -type f 2>/dev/null:查找所有SUID权限的文件。SUID文件执行时会以文件所有者的身份运行。同样发现了/usr/bin/find。
4.2 利用SUID的Find命令提权
既然find命令具有SUID权限,且属于root,那么我就可以利用它来执行任意命令,并且这些命令将以root权限运行。
利用方式:
# 在获得的Webshell中执行 find / -exec whoami \; # 如果返回 root,则证明可以利用 # 更直接地,获取一个root shell find / -exec /bin/sh -p \; # 或者 touch /tmp/rootme find /tmp/rootme -exec /bin/sh -p \;执行find / -exec /bin/sh -p \;后,我成功获得了root权限的shell。-p参数在某些系统上用于保留特权模式。
4.3 定位并获取Flag
提权到root后,寻找Flag就简单了。通常Flag文件的名字或路径在题目描述中会有提示,或者遵循惯例(如/flag,/root/flag.txt)。我使用以下命令进行搜索:
find / -name "*flag*" -type f 2>/dev/null find / -name "*.txt" -exec grep -l "flag{" {} \; 2>/dev/null最终,我在/root/目录下找到了一个名为final_flag.txt的文件,使用cat命令成功读取到Flag内容。
5. 安全加固建议与反思
作为攻击方完成任务后,换位到防守方,我需要针对发现的漏洞提出加固建议。这部分在竞赛中同样占分。
5.1 针对已发现漏洞的加固
文件上传漏洞:
- 白名单校验:不仅校验MIME类型,更要对文件扩展名实施严格的白名单机制(如只允许
.jpg,.png,.gif)。 - 文件内容校验:使用
getimagesize()、exif_imagetype()等函数验证文件确实是有效的图片。 - 重命名与隔离:上传的文件应使用随机生成的文件名(如UUID),并去除原扩展名,统一赋予白名单内的安全扩展名。将上传目录设置为不可执行脚本(通过配置Web服务器或使用
.htaccess禁止PHP解析)。 - 文件类型检测:可以考虑使用更底层的文件头魔数(Magic Number)检测。
- 白名单校验:不仅校验MIME类型,更要对文件扩展名实施严格的白名单机制(如只允许
文件包含漏洞:
- 避免动态包含:尽量避免使用用户输入直接作为文件路径进行包含操作。
- 固定包含路径:如果必须动态包含,应使用白名单机制,只允许包含预设的几个文件。
- 过滤输入:对输入参数进行严格过滤,禁止目录穿越字符(
../,..\)和空字节(%00)。 - 设置
open_basedir:在PHP配置中设置open_basedir,将PHP可操作的文件限制在指定目录树内。
日志文件包含:
- 权限控制:确保Web服务器日志文件(如
access.log)对Web运行用户(如www-data)仅有读取权限,绝无写入权限。更好的做法是将日志文件放在Web目录之外。 - 日志内容过滤:对写入日志的内容进行过滤,转义或删除可能被解释为代码的特殊字符。
- 权限控制:确保Web服务器日志文件(如
权限配置不当(SUID提权):
- 最小权限原则:定期审计系统SUID/SGID文件,使用
find / -perm /6000 -type f列出所有此类文件,移除非必要的SUID/SGID位。 - 审查sudoers:严格管理
/etc/sudoers文件,遵循最小授权原则,避免赋予普通用户过高的、无密码的sudo权限。对于像find这样的强大命令,尤其要谨慎。
- 最小权限原则:定期审计系统SUID/SGID文件,使用
5.2 纵深防御体系建设建议
除了修补具体漏洞,还应从体系上思考:
- 输入输出全面过滤:对所有用户输入进行验证、过滤和转义。输出到HTML时进行HTML实体编码,防止XSS;输出到系统命令时使用参数化或白名单。
- 错误信息处理:关闭生产环境的PHP错误显示(
display_errors = Off),使用自定义错误页面,避免泄露路径、数据库结构等敏感信息。 - 定期安全更新与审计:保持操作系统、Web服务器、PHP及所有依赖库更新至最新稳定版。定期进行代码安全审计和渗透测试。
- 网络层防护:使用WAF(Web应用防火墙)防御常见Web攻击。对服务器进行适当的网络隔离。
回顾这道赛题,从信息搜集到源码审计,再到漏洞串联利用,最后提权取Flag,是一个完整的渗透测试流程。它考察的不仅是某个漏洞的利用,更是选手在压力下的逻辑思维、知识串联和问题解决能力。独自解题时,保持冷静、有条理地层层推进,比掌握所有漏洞利用技巧更重要。每一次“踩坑”后的复盘,都是技术视野的一次拓宽。