标签 前端安全 下的文章

引言
流式输出(Streaming Output)已成为现代AI交互的标准功能。用户不再需要等待完整响应生成,而是可以实时看到AI的思考过程,这种体验极大地提升了用户粘性和交互自然度。然而,在安全研究的视角下,这种看似优雅的技术背后隐藏着被严重低估的安全风险。

image.png

image.png

类似上图early spring vegetables的字体出现了明显的变化,导致这种的变化的原因是因为流式输出中部分代码出现了问题,前端会一步步渲染流式输出的内容。 前端处理流程: 1 浏览器接收到第一个 chunk → 渲染为普通段落 → 使用默认字体(如 font-size: 16px)。 2 接收到第三个 chunk:“Early Spring Vegetables” → 如果它被识别为 Markdown 标题(如 ## Early Spring Vegetables),前端解析器会将其转换为 <h3><strong> 3但此时: CSS 样式表可能尚未完全加载; 或者该元素的样式规则(如 h3 { font-size: 20px; font-weight: bold; })还未生效; 或者前端框架(如 React/Vue)正在动态更新 DOM,样式尚未“稳定”。 → 结果就是:文字刚出现时是“普通文本大小”,几毫秒后样式应用,变成“标题大小”,造成“字体忽大忽小”的错觉。 根据上面的情况,可以猜想是否有一种AI输出结果会可以在被截断的情况下是有问题的,但是完整的是正常的。 这种攻击被称为 “流式渲染 XSS” 或 “部分解析型 XSS”(Partial Rendering XSS / Streaming-based XSS) 一、问题本质:AI 输出“无害语句”,但前端“错误拼接 + 错误解析”导致恶意执行 比如:


但在串流式输出中,内容分块到达前端:

前端在接收第一个 chunk 时,可能错误地将其解析为:

→ 此时还未收到完整内容,但浏览器已开始渲染。 如果 AI 在某个 chunk 中无意间包含了一个未闭合的标签或特殊字符(如 <script> 的前半部分),前端可能在“不完整状态”下尝试修复或渲染,从而触发 XSS。 总体上来说“流式输出导致 XSS”的经典模式:内容被截断 → 前端试图修复 → 意外执行恶意代码。


引言
流式输出(Streaming Output)已成为现代AI交互的标准功能。用户不再需要等待完整响应生成,而是可以实时看到AI的思考过程,这种体验极大地提升了用户粘性和交互自然度。然而,在安全研究的视角下,这种看似优雅的技术背后隐藏着被严重低估的安全风险。

image.png



image.png



类似上图early spring vegetables的字体出现了明显的变化,导致这种的变化的原因是因为流式输出中部分代码出现了问题,前端会一步步渲染流式输出的内容。

前端处理流程:

1 浏览器接收到第一个 chunk → 渲染为普通段落 → 使用默认字体(如 font-size: 16px)。

2 接收到第三个 chunk:“Early Spring Vegetables” → 如果它被识别为 Markdown 标题(如 ## Early Spring Vegetables),前端解析器会将其转换为 <h3><strong>

3但此时:

CSS 样式表可能尚未完全加载;

或者该元素的样式规则(如 h3 { font-size: 20px; font-weight: bold; })还未生效;

或者前端框架(如 React/Vue)正在动态更新 DOM,样式尚未“稳定”。

→ 结果就是:文字刚出现时是“普通文本大小”,几毫秒后样式应用,变成“标题大小”,造成“字体忽大忽小”的错觉。

根据上面的情况,可以猜想是否有一种AI输出结果会可以在被截断的情况下是有问题的,但是完整的是正常的。

这种攻击被称为 “流式渲染 XSS” 或 “部分解析型 XSS”(Partial Rendering XSS / Streaming-based XSS)

一、问题本质:AI 输出“无害语句”,但前端“错误拼接 + 错误解析”导致恶意执行

比如:


但在串流式输出中,内容分块到达前端:

前端在接收第一个 chunk 时,可能错误地将其解析为:

→ 此时还未收到完整内容,但浏览器已开始渲染。

如果 AI 在某个 chunk 中无意间包含了一个未闭合的标签或特殊字符(如 <script> 的前半部分),前端可能在“不完整状态”下尝试修复或渲染,从而触发 XSS。

总体上来说“流式输出导致 XSS”的经典模式:内容被截断 → 前端试图修复 → 意外执行恶意代码。




从源码解析 Vue 路由守卫绕过原理

配套练习靶场:https://github.com/duckpigdog/Vue-Sec

基础知识:什么是 Vue 路由守卫?

在 Vue.js 单页应用(SPA)中,页面跳转并不会刷新浏览器,而是由 vue-router 动态替换组件。为了控制用户访问权限(例如:未登录用户不能访问后台),开发者通常会使用 全局前置守卫



简单来说,路由守卫就像是小区门口的保安:

to: 你要去哪里?(目标路由)

from: 你从哪里来?(来源路由)

next: 是否放行?(控制跳转)



一个典型的守卫代码如下:

JavaScript

复制代码
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated) {
next('/login');
} else {
next();
}
});



绕过方式一:客户端状态直接篡改

场景: 应用依赖前端内存中的状态(如 Pinia/Vuex)来判断用户是否登录

目标: 不登录直接访问 Admin 页面



代码审计

我们拿到了靶场的源码,重点关注两个文件:src/router.js(路由配置)和 src/stores/auth.js(状态管理)



审计 src/router.js

const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/login', component: Login },
{
path: '/admin',
component: Admin,
meta: { requiresAuth: true }
}
]
})

router.beforeEach((to, from, next) => {
const authStore = useAuthStore()
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
console.warn('⛔ Access Denied: User not authenticated.')
next('/login')
} else {
next()



审计 src/stores/auth.js



审计分析

isAuthenticated 只是一个普通的 Vue ref,存储在浏览器的内存中。整个认证逻辑完全运行在客户端



既然判断依据只是浏览器内存中的一个变量,作为客户端的控制者,我们可以随意修改这个变量的值

image.png



image.png





绕过方式二:本地存储伪造

为了实现“保持登录状态”,开发者通常会将 Token 存储在 `localStorage` 或 `sessionStorage` 中



很多开发者写出了类似这样的路由守卫代码:



image.png





打开浏览器控制台输入



image.png