你正在参加一场线上CTF挑战,打开一个名为“Unblur me”的网页,迎面而来的是“Calculus Review”测试。规则说得明明白白:连续答对500道随机生成的微积分求导题,进度条才会走到头,模糊的图片才会变得清晰,秘密才能揭晓。一旦答错,计数器直接归零。如果你动手用纸笔一道道算,不仅要累到吐血,而且大概率中途手滑——这怎么看怎么像一场折磨人的耐力赛。但问题来了:这个“500题”的闸门,真的在服务端被锁死了吗?

先看看页面源代码,逻辑全写在客户端JavaScript里。核对答案的函数checkAnswer()中,用户输入被parseInt后与当前答案比较,正确就correctCount++,然后检查if (correctCount >= 500),满足条件就移除图片的CSS滤镜,同时开放指针事件。这个correctCount就是一个普普通通的浏览器变量,不存在任何服务器校验。也就是说,只要你愿意,打开开发者控制台,直接改掉这个数字,浏览器立马以为你已经是微积分之神,图片随之清晰,连一道题都不用碰。解题流程完全被戏耍了。

更离谱的还在后面。图片真被模糊过吗?loadSecretImage()函数在页面加载时无条件执行,直接从/api/v1/internal/fetch-config-blob接口抓取完整的图片二进制数据,赋给一个blob URL,再塞进的src。那个模糊效果,全靠CSS的一行filter: blur(20px)在浏览器渲染层打的障眼法。图片本身从网络传输到DOM,从头到尾都是高清的,没受过一个像素的委屈。所谓“解锁”,不过是摘掉一副本来就架在浏览器上的毛玻璃眼镜。

这下思路就异常清晰了:连浏览器都不用开,更别说什么做题、改变量。直接用curl调用那个内部接口,把返回的blob存成文件:
curl https://broncoctf-unblur-me.chals.io/api/v1/internal/fetch-config-blob -o out
file一查,是1648×928的PNG图片,RGBA色彩,非隔行扫描。打开这张图,flag赤裸裸地印在画面里:bronco{1_wouldnt_m@k3_you_do_c@lculus}。整个过程中,根本没有CSS滤镜插手的机会,因为渲染这一步被彻底跳过了。

这件事的根源,可以凝成一句话:永远别把客户端逻辑当成安全闸门。计数器、完成条件、甚至文件展示的模糊效果,只要是跑在用户浏览器里的JS实现的,都可以被绕过——不用复杂漏洞,不用精心构造payload,只需直接请求那个本该被“保护”的后端端点。开发者的错误在于,把显示层的视觉特效,误当成数据层的访问控制。这种“红杏出墙”式的设计,在比赛中带来了一个令人哭笑不得的解法,却也照出了现实中Web应用常见的疏忽:API只要没做真正的鉴权,前端的任何“任务”都只是一道虚掩的门。