|
|
本帖最后由 Codishek2005 于 2026-7-20 00:01 编辑
通过论坛的自由模块,你可以为自己的喵玉殿个人主页添加酷炫的同款樱花特效,也可以执行其他任意脚本以实现各种功能(甚至完全渲染成另一个完全不同的页面)。接下来我们将深入此背后的机制,了解如何利用论坛的设计缺陷在浏览器上执行任意脚本。
自由模块:自由的脚本
在喵玉殿个人主页的装扮空间里存在这样一个功能:自由模块。这个模块可让你在里面编写任意文本,同时也包括HTML代码,在模块里编写的HTML代码会渲染到页面上。正是此特性使得实现执行脚本成为可能。
但此时就遇到了第一个困难:我们不能直接在模块里编写<script>。自由模块并非直接将里面的内容渲染到页面上,而是会进行一些预处理,其中包括将换行符替换为<br>标签、移除<script>标签。而要实现樱花特效,就需要依赖在页面上执行自定义代码创建Canvas,因此我们必须另辟蹊径。
Onload 是你所需要的一切
在HTML里有一个元素属性叫onload,它指定了当元素包含的资源完成加载、触发load事件时,所要执行的代码。
而正巧合的是,我偶然发现喵玉殿并不会移除掉此onload属性!
因此出现了一个思路,我们可以利用<img>图片元素。至于是什么图片无所谓,我们重点关心的是其onload属性,可以往onload里写入精心设计的脚本,以此来实现Canvas渲染。
因为图片展示的内容对我们不重要,所以最好是使用足够小的图片,搭配CSS属性display: none以避免图片元素干扰其他内容。为了尽可能减小加载图片带来的影响,我们可以使用Data URL(本质上是将文件内容转换为一种特殊的链接)直接嵌入图片到HTML代码中,以此来免去网络加载图像的开销。
现在我们构建了一个极限小的单像素GIF图像,使用Data URL编码:
data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7
然而接下来还有第二关......
字数限制:自由的边界
有一次我在模块里测试各种各样的脚本,发现有时候脚本会不工作。最终发现是模块有字数限制,我粘贴进去的脚本全部被截断了。
这可不妙,仅靠1000字的代码可没法实现完整的樱花飘落动效,更何况前面的<img>元素就已经耗掉了约100字。即使有可能通过各种奇技淫巧使用剩下900字实现樱花飘落,我们也不打算去探讨怎么实现,毕竟现在讨论的主题可不是 IOCCC(其实就是我菜)
同源策略与CORS
既然我们没法靠900字的空间来编写代码,那么就需要找出一种方式能足够容纳所有的代码。一部分人或许觉得可以将脚本随便扔到GitHub之类的地方然后再去引用,但实际上这条路行不通,因为这跟接下来提到的同源策略(Same-Origin Policy)有关。
现代浏览器为了安全,引入了同源策略机制。在默认情况下浏览器会阻止跨域脚本请求,而GitHub并没有显式设置CORS请求头,意味着你无法从喵玉殿请求位于GitHub上的资源。当然,我们可以找一个同源策略限制宽松的托管网站,或者也可以自己搭建网站并允许从喵玉殿访问。但如果我告诉你,其实喵玉殿相册就能存储脚本呢?
图像之外
现在有一张特殊的图像,如果直接打开看的话会发现是一片空白。
但如果你用16进制编辑器来看的话,就会发现里面的蹊跷:
而这正是利用了论坛对图像校验的机制!
当你从论坛相册上传图片时,论坛会校验图片是否为允许的文件类型,以及能否正常加载,这意味着你上传一堆垃圾数据会失败。同时相册上传的文件大小限制是500KiB,超出这个限额之后也会失败。
绝大多数主流的图像格式,都会有一个结束标识来告诉图像解码器“图像已经完成加载”。在大部分解码器实现里,通常读取到这个标识就停止解析了。对于PNG图像,这个结束标识是IEND块:
而恰巧的是,论坛也不在乎PNG IEND块之后的数据!只要是可正常读取的图片,并且不超过大小上限,在IEND块之后无论填充什么数据都能正常上传。
到此,我们已经有了一个完整的设计思路了。先将完整的樱花动效代码存储在图片尾部,然后在主页的自由模块里编写加载器用于请求图像文件,截获图像尾部之后的代码并执行。对于樱花动效的代码,500KiB空间绰绰有余;对于自由模块的加载器,1000字也足够用。
为了节省流量,图像尾部的脚本还可以跑一遍GZIP压缩。在自由模块加载器代码里使用现代浏览器的DecompressionStream API去解压GZIP数据流,而这部分解压的代码塞进1000字里也正好足够。
樱花飘落🌸
现在,我们已经有了在个人主页上执行任意代码的能力,接下来的一切都好办了。
我们通过脚本在<body>最后添加一个<canvas>元素,在Canvas里面绘制樱花花瓣,这样樱花飘落的画面就会叠加到整个页面上(确保你的绘制上下文启用了Alpha)。
花瓣绘制
要绘制樱花花瓣,我们可以通过两个三次贝塞尔曲线来拟合,其中一个绘制左半部分,另一个是右半部分。
此处是我摸索出的观感比较好的坐标参数:
飘落动效
为每一片花瓣分配随机的移动速度、转速、横向摆动频率,在requestAnimationFrame()里每一帧更新位置、角度并重绘,即可让花瓣动起来。
不要忘记Delta Time,这是在不同设备上保持相同速度观感的关键!
尾声:互联网安全的启示
尽管现代浏览器在安全机制上采用了严格的限制,但也只能减少一部分网站上的用户生成内容的威胁。在服务端层面上,服务端始终不应该信任用户的每一个输入。
也许这部分的内容会在论坛架构发生改变后不适用,也有可能一直保持现状,谁又知道未来是怎样的呢?
灵感来源
其实我一开始都不知道个人主页还有自由模块,直到我看到了 常荣棉 (UID:273332) 的个人主页,才开始细究原理。
程序员的直觉告诉我,这头像能转起来肯定有蹊跷(笑) |
评分
-
查看全部评分
|