122
“大家好,我叫陈雨晴。”
声音从话筒传出去,在会场里回荡了一下。台下黑压压的,灯光太亮,她看不清任何一张脸。只能看到第一排那几个人——正会长侧着头跟旁边的人说话,没看她。工作人员站在舞台侧边,手里还拿着对讲机,刚才架走胡会长的两个黑西装已经不在了。
雨晴把手放在讲台边缘,稳住自己。手心还是湿的,她在裤子上蹭了一下,但没人能看到。
“我今天要讲的题目是——我是怎么挖漏洞的。”
她按下翻页笔。PPT第一页,标题,她的名字,一个简单的头像——卡通版的,戴着耳机,面前是一台电脑。这是她在群里用过的头像,苏晚帮她画的,说“这样比较像你”。台下有人笑了一声,不知道是觉得可爱还是觉得幼稚。
“我先简单介绍一下自己。”雨晴顿了顿,声音比自己预想的稳。“我今年十五岁,郑州人,初二学生。做网络安全渗透测试大概半年多,做了五个项目。之前参加过一个小比赛,拿了第三名。”
台下有了一些声音,不是喧哗,是窃窃私语。十五岁,初二,半年五个项目。这些数字她自己说出来都觉得不太真实,但都是真的。她没看台下,盯着PPT,继续翻。
“今天我想分享的是一个OA系统的渗透测试案例。这个系统是我做的第五个项目,也是我目前做过的最完整的一个,从信息收集到漏洞挖掘到报告输出,整个流程都走了一遍。”
第二页,项目背景。
“目标是一个企业OA系统,功能包括用户登录、审批流程、文件上传、个人信息管理等等。测试范围是外网,甲方没有提供任何账号和权限,属于黑盒测试。”
她放了一张目标系统的登录页截图。
“第一步是信息收集。”
第三页,信息收集。
“我首先用工具扫描子域名。跑了一圈,发现了一个后台管理系统的子域名——admin.xxx.com。这个子域名在主站上没有链接,搜索引擎也没有收录,是通过暴力枚举找到的。”
她放大截图,展示那个子域名。
“访问这个子域名,是一个登录页面。没有验证码,没有防暴力破解机制。我试了几个弱口令——admin/admin,admin/123456,test/test。都不行。”
“然后我扫了它的目录。用dirsearch跑了一遍,发现了一个/backup/目录,目录列表是开放的。”
第四页,截图。浏览器地址栏显示“admin.xxx.com/backup/”,页面上一堆文件名,.sql和.zip。
“这里有一个数据库备份文件,2024年12月的。”她圈出其中一个文件名。“我下载了,打开一看,里面有用户表。用户表里存的是明文密码。”
台下有人“啧”了一声。
“这不是漏洞吗?是。但这是信息收集阶段发现的,我先记下来,没有马上报。”
第五页,回到主站。
“主站有一个留言板功能,用户可以提交留言,管理员在后台审核。我在留言内容里输入了一段XSS测试代码——,提交。”
“等了大概两分钟,刷新页面,弹窗了。”
台下有人笑了。
“这是一个存储型XSS。留言被存入数据库,管理员每次打开审核页面都会执行那段脚本。这个漏洞的危害在于,如果攻击者植入恶意脚本,可以窃取后台管理员的Cookie,实现权限劫持。”
她放了一张弹窗的截图,alert(1)显示在屏幕上。台下的笑声还没停,她继续说。
“我把这个漏洞记下来,继续测。”
第六页,文件上传。
“个人中心里有头像上传功能。我上传了一张正常的图片,jpg格式,成功。然后我试了PHP文件,前端拦截了,提示‘文件类型不允许’。”
她放了一张Burp Suite的截图。
“但我改了请求包——把Content-Type改成image/jpeg,把文件后缀改成.jpg,文件内容还是PHP代码。上传,成功。拿到路径,访问,PHP代码被执行了。”
“这个漏洞的严重程度是高。攻击者可以上传任意恶意脚本,获取服务器权限,进而控制整个系统。”
台下安静了。有人在本子上记东西。
“但这不是我今天要讲的重点。下面这个才是。”
第七页,第一个高危漏洞——越权查看订单。
“OA系统里有一个功能,员工可以查看自己提交的订单。URL是/order/detail?id=12345。我试着把id后面的数字改成12344——看到了另一个员工的订单。”
她放了两张截图。第一张:id=12345,显示她自己的订单。第二张:id=12344,显示别人的订单。
“这是一个典型的越权漏洞——不安全的直接对象引用。攻击者可以通过遍历订单ID,查看所有员工的订单信息,包括订单金额、审批意见、个人信息。”
“这个漏洞的根源是后端没有对用户身份做校验。它只验证了用户是否登录,但没有验证这个订单是否属于当前用户。”
“修复方案很简单:在后端增加归属校验,查询订单之前先判断当前用户的权限级别。”
“这个我花了大概一个小时就找到了。”
第八页,第二个高危漏洞——审批流程的逻辑缺陷。
“这个漏洞我花了两天时间才完全摸清。”
她放了一张流程图,三个角色:普通员工、部门主管、总经理。
“审批流程是这样的:员工提交订单,主管审批,总经理终审。这个逻辑本身没问题。但我发现了一个时序问题。”
“在主管审批通过之后,总经理终审之前,员工还可以修改订单内容。”
她放了两张截图。第一张:订单金额100元,主管审批通过。第二张:订单金额被修改为10000元,总经理审批通过。
“主管审批的时候看到的是100元,他批了。员工在主管批完之后把金额改成了10000元,总经理看到的是10000元,也批了。订单生效,金额10000元。”
她停顿了一下,让台下消化。
“主管审批的那个100元记录还在,数据库里有两条记录——审批记录是100元,订单执行是10000元。但业务系统最终执行的是10000元。”
台下有人举手。雨晴看了一眼,是个年轻人,戴眼镜。
“你问。”雨晴说。
“审批通过后订单状态应该会变成‘已审批’,员工还有权限修改吗?”
“问得好。”雨晴点头。“这就是问题所在。系统的设计是:审批通过后订单状态确实变成了‘已审批’,但‘已审批’这个状态并没有锁定订单的编辑权限。员工的修改权限只在前端被隐藏了,后端没有做限制。”
“所以我用Burp Suite直接发了一个修改请求——绕过前端,后端接收了,订单金额被改了。”
“这是一个典型的逻辑漏洞,涉及到业务流程的设计缺陷。审批通过后订单应该被锁定,但这个系统没有。”
那人点了点头,坐下了。
“我继续。”
第九页,第三个高危漏洞——并发竞态条件。
“这是最难的一个。我花了三天时间。”
她放了一张流程图,展示并发请求的顺序。
“订单提交的时候,系统会先检查库存,然后再扣减。正常的流程是:检查库存->扣减->生成订单。如果检查库存和扣减这两个操作不是原子性的,在高并发的情况下就可能出问题。”
“我写了一个多线程脚本,同时发送一百个请求,每个请求都下单同一个商品。库存只有十件。”
“正常情况下,只有前十个人能成功,后面的九十个人会提示‘库存不足’。但我在测试的时候发现,最终生成的订单有十五个——超卖了五件。”
台下有了声音。有人往前倾了倾身子。
“为什么会出现这种情况?因为检查库存和扣减库存之间有一个时间窗口。当大量请求同时到达的时候,假设有十五个请求同时通过了库存检查,然后系统才开始扣减。但库存只有十件,前十个扣减成功之后,后五个也应该失败——但因为有十五个请求同时进入了扣减流程,系统不知道谁先谁后,于是全部扣减了。”
她把截图放大,展示那十五个订单的创建时间。时间戳精确到毫秒,十五个订单几乎在同一毫秒内生成。
“这个漏洞的严重程度也是高。攻击者可以利用竞态条件,在秒杀活动、优惠券领取、转账等场景中获利。”
“修复方案有两种。一种是使用数据库的行锁,在事务中加for update。另一种是使用Redis的原子操作,先扣减再检查,把检查和扣减合并成一个原子操作。”
台下有人点头。第一排的正会长不知道什么时候抬起了头,正看着她。
“这个漏洞,我花了两天时间复现,因为不是每次都能成功。成功率大概百分之三十。后来我调整了并发数,把一百个请求改成五十个,成功率提高到百分之六十。最终我在报告里写了‘存在竞态条件漏洞,复现成功率约60%’。”
“甲方后来回我说‘这个场景在实际业务中不太可能出现’。我说‘但有可能’。甲方说‘知道了’。”
台下有人笑了。这次是那种“懂的都懂”的笑。
“好,继续。”
第十页,第四个高危漏洞——短信验证码的重置绕过。
“这是一个密码重置功能的漏洞。”
她放了一张截图,密码重置页面——输入手机号,获取验证码,输入验证码,设置新密码。
“正常的流程是:用户输入手机号,系统发送验证码,用户输入验证码,系统验证通过后允许设置新密码。”
“但我发现了一个问题。在第三步‘输入验证码’的请求里,系统只校验了验证码是否正确,但没有校验这个验证码是发给谁的。”
她把请求包放大。
“也就是说,我用自己的手机号申请验证码,然后把这个验证码用在别人的账号上,系统接受了。”
“我测试了一下。先用我的手机号申请验证码——收到短信,验证码是123456。然后我拿着这个验证码,去重置另一个账号的密码。系统提示验证码正确,允许设置新密码。”
“我成功重置了一个同事的账号密码。”
台下有人低声说了句“卧槽”。
“这个漏洞的严重程度非常高。攻击者可以重置任何知道手机号的账号的密码,完全接管账户。”
“修复方案也很简单:在验证码校验的环节,增加手机号的校验。验证码不仅要匹配,还要匹配它是发给谁的。”
“这个漏洞是我在测试的时候无意中发现的。当时我在测别的东西,抓了一个包,发现请求参数里只有验证码,没有手机号。我就试了一下——成功绕过。”
第十一页,第五个高危漏洞——权限维持的隐藏后门。
“最后一个漏洞,是我在提权阶段发现的。”
“通过文件上传漏洞拿到webshell之后,我需要维持权限。上传了一个普通的webshell,很快被杀了——服务器上有终端检测。”
她放了一张截图,显示webshell被删除的日志。
“于是我换了一个思路。我不上传文件,利用系统自带的组件来执行命令。这个系统有一个功能,可以调用系统命令生成PDF报告。调用的是/usr/bin/phantomjs。”
“我发现这个参数可以拼接任意命令。在PDF生成的API里,参数filename的值是用户可控的。我传了一个值——test.pdf; whoami。后台执行的命令变成了/usr/bin/phantomjs render --file=test.pdf; whoami。”
“whoami执行了,返回了www-data。”
她放了一张截图,命令执行的结果。
“这是一个命令注入漏洞。因为后端直接拼接了用户输入到系统命令中,没有做任何过滤。攻击者可以执行任意系统命令,比如下载反弹shell脚本、添加后门用户、横向移动。”
“这个漏洞的严重程度是严重。配合文件上传漏洞,攻击者可以完全控制服务器。”
她停顿了一下,翻到下一页。
“修复方案:使用参数化接口,不要拼接命令。如果必须拼接,对用户输入做严格的白名单校验。”
第十二页,修复建议汇总。
“我把这五个高危漏洞总结了一下。”
她放了一张表格。
漏洞类型 严重程度 修复状态
越权查看订单 高危 已修复
审批流程逻辑缺陷 高危 未修复
并发竞态条件 高危 未修复
短信验证码重置绕过 高危 已修复
命令注入 严重 已修复
“两个修了,两个没修,一个严重修了。文件上传那个漏洞,甲方说‘知道了’之后又测了一次,还是没修。审批流程的逻辑缺陷,甲方说业务上需要员工修改订单,不能锁定,所以不修。竞态条件,甲方说并发量不大,风险可控。”
台下有人摇头。
“这就是现状。”雨晴说。“报告里写了,风险告知了,但修不修是甲方的事。我们只能把风险描述清楚,把修复建议写明白,剩下的不是我们能控制的。”
第十三页,一些感想。
她翻到最后一页。标题是“一些感想”,下面只有四行字。
“漏洞不会自己消失。”
“修了才是真的修。”
“不要只做挖洞的工具,要知道你在挖什么。”
“以及——报告写得再好,甲方不修等于零。”
台下安静了一秒。然后有人鼓掌,不是零星的,是成片的。雨晴站在台上,看着那些模糊的脸,手心还在出汗。她鞠了个躬——不标准,弯得太低了,几乎快九十度。
正会长从第一排站起来,走上台,接过话筒。他笑了笑,“感谢陈雨晴的分享。十五岁,非常年轻,非常专业。大家有问题可以到后台交流。”
雨晴走下台。舞台侧边的工作人员给她递了一瓶水。“讲得不错。”那人说。雨晴接过来,“谢谢”。她拧开盖子喝了一口,水是温的。
站在幕布后面,她靠着墙。心跳很快,比上台的时候还快。讲完了。十五分钟,她觉得像过了一个小时。她掏出手机,群里何意味在问“讲完了吗”。她回“讲完了”。何意味发了一个放烟花的动效图。苏晚“怎么样”。雨晴想了想。“还行”。
她没说自己手心出汗,没说自己忘了翻页笔的下一页在哪。没必要。
“陈雨晴?”
雨晴抬起头。一个穿着深灰色西装的男人站在她面前,四十岁左右,头发很短,戴着一副无框眼镜,手里拿着一个笔记本。
“我是众安科技的技术负责人,姓周。”他递过来一张名片。“你的分享很有价值,特别是那个竞态条件的案例,思路很清晰。”
雨晴接过名片。众安科技,网络安全事业部,技术总监,周正平。
“我们公司在招技术顾问,兼职的。不需要全职坐班,远程就行。主要工作是对外输出安全能力,做技术咨询,给甲方出方案。你感兴趣吗?”
雨晴愣了一下。“我?”
“你。你做的项目量够了,漏洞类型覆盖得也全。那个竞态条件的复现思路,很多做了三年的工程师都未必能想到。”周正平笑了一下。“当然,不是全职,不耽误你上学。三个月来公司开一次会就行,其他时间远程。”
“我还在上学。”
“我知道,十五岁。简历上写了。”
雨晴看着那张名片。众安科技,她知道这家公司,之前在看招聘信息的时候刷到过。总部在上海,做安全服务的,圈子里的口碑不差。
“我可以考虑一下吗?”
“当然。”周正平从笔记本上撕下一张纸,写了一行字递给她。“这是我微信。想好了加我。”
雨晴接过去,折好放进口袋里。
“谢谢周总。”
“不客气。你讲的那个并发竞态,回头我把我们的测试环境发你,你帮我们看看有没有类似的问题。有偿的。”
“好。”
周正平走了。雨晴靠着墙,把口袋里的纸条又拿出来看了一眼。众安科技。技术顾问。她觉得有点不真实——五分钟前她还在台上讲怎么挖漏洞,现在有人给她递名片了。她掏出手机,在群里打了一行字。“有人给我递名片了。”苏晚“谁”。雨晴“一家安全公司的技术总监”。何意味“姐你要跳槽了?”雨晴“不是跳槽,是兼职”。深海鱼“牛”。
她没回。把手机放回口袋。
她走出后台,会场里的人已经开始散了。几个年轻人走过来,其中一个戴着鸭舌帽的男生问她“你是刚才讲那个竞态条件的人吗”。雨晴说“是”。那人说“你那个复现思路可以再讲一下吗,我回去也想试一下”。雨晴大概讲了两分钟。旁边又围过来两个人,有人问“你那个Burp Suite的插件用的是什么”,有人说“你那个命令注入的payload可以分享一下吗”。
雨晴一个一个回答。讲了大概十几分钟,人散了。她站在空荡荡的会场里,头顶的灯还亮着。她看了一眼手机,下午五点四十。
晚上还有一场圆桌论坛,她不想听了。走出会场,外面天还没黑,四月的上海,黄昏来得比郑州早一点。她站在会展中心门口,看着对面的高楼,玻璃幕墙反射着夕阳的光,橘红色的。风吹过来,有点凉。
她掏出手机,给林听夏发了条消息。“讲完了。”
林听夏回得很快。“怎么样?”
“有人递名片了。”
“谁?”
“一家安全公司的技术总监。让我去做技术顾问。”
林听夏发了一个句号。雨晴知道这个句号的意思——她在想,然后才会回复。过了一会儿,林听夏发了一条。“你答应了?”
“没。说考虑一下。”
“那你怎么想的。”
雨晴想了想。“不知道。先回去再说。”
“嗯。”
雨晴把手机放回口袋。她站在路边,等出租车。四月的上海,路边有花开了,粉色的,叫不出名字。她看了一会儿,车来了。上车,回青旅。
明天还有半天,下午返程。她不想逛了,只想回去。
躺在青旅的床上,室友还没回来,房间里只有她一个人。雨晴把口袋里的纸条拿出来,看了第三遍。众安科技,技术顾问。她把纸条折好,放回口袋。然后翻了个身。
橘子皮不在。她有点想猫了。
手机震了。周正平发来的微信——她刚才加了好友,对方通过得很快。
“陈雨晴,刚才在会场没来得及细说。我们这个岗位需要出差,可能要去全国各地的客户公司,帮忙做代码审计、应急响应、安全加固。不坐班,但出差比较频繁。你能接受吗?”
雨晴看着那行字。出差。全国各地。她才十五岁,一个人去客户现场,能行吗?她没立刻回,想了一会儿。
“我还没成年,有些场合可能不太方便。”
周正平很快回了:“这个不用担心,我们会安排同事陪同。主要看你的技术能力。你基础扎实,实战经验也有,我们这边缺的就是能干活的人。你明天有时间吗?来公司面聊一下,具体工作内容、待遇、合作方式,当面谈。”
明天。雨晴原计划明天下午返程。她把12306打开看了一眼——周日返程票还有,下午三点的,到郑州晚上七点多。周一要上课,不能耽误。
“明天上午可以吗?我下午的火车回郑州。”
“可以。十点?我发你地址。”
“好。”
她把车票改签了。原来下午三点的票没了,只剩下午五点多的,到郑州晚上九点多。周一早上七点就要起,睡不了几个小时。但她没别的选择,改签,付了手续费。
发完截图,她跟林听夏说了:“明天上午去面试那家公司。车票改到下午了,晚上到。”
林听夏回:“那你注意安全。到了给我发消息。”
“嗯。”
群里何意味在问:“姐,明天回来吗?”
“晚上到。”
“我去接你。”
雨晴想了想,打了个“不用”,又删了。“你来吧。”
何意味发了一个猫点头的表情。苏晚“你俩现在住这么近,天天见”。何意味“没有天天见,姐不见我”。雨晴没回。她把手机放到枕头旁边,关了灯。
明天面试。她在脑子里过了一遍——众安科技,技术顾问,出差改代码。她没做过代码审计,只做过渗透测试。代码审计是看源码找漏洞,渗透测试是黑盒打点。两者有重叠,但不完全一样。她不确定能不能胜任。但周正平说“缺能干活的人”,说明他们不是找她来做样子的。她翻了个身,被子拉到下巴。室友还没回来,房间里很安静。她闭上眼睛,想着明天的事。