I. 代码实现

 

1.1 原生DOM API的安全操作

1.1.1【必须】HTML标签操作,限定/过滤传入变量值

  • 使用innerHTML=outerHTML=document.write()document.writeln()时,如变量值外部可控,应对特殊字符(&, <, >, ", ')做编码转义,或使用安全的DOM API替代,包括:innerText=
// 假设 params 为用户输入, text 为 DOM 节点
// bad:将不可信内容带入HTML标签操作
const { user } = params;
// ...
text.innerHTML = `Follow @${user}`;

// good: innerHTML操作前,对特殊字符编码转义
function htmlEncode(iStr) {
	let sStr = iStr;
	sStr = sStr.replace(/&/g, "&amp;");
	sStr = sStr.replace(/>/g, "&gt;");
	sStr = sStr.replace(/</g, "&lt;");
	sStr = sStr.replace(/"/g, "&quot;");
	sStr = sStr.replace(/'/g, "&#39;");
	return sStr;
}

const { user } = params;
user = htmlEncode(user);
// ...
text.innerHTML = `Follow @${user}`;

// good: 使用安全的DOM API替代innerHTML
const { user } = params;
// ...
text.innerText = `Follow @${user}`;

1.1.2【必须】HTML属性操作,限定/过滤传入变量值

  • 使用element.setAttribute(name, value);时,如第一个参数值name外部可控,应用白名单限定允许操作的属性范围。

  • 使用element.setAttribute(name, value);时,操作a.hrefifame.srcform.actionembed.srcobject.datalink.hrefarea.hrefinput.formactionbutton.formaction属性时,如第二个参数值value外部可控,应参考JavaScript页面类规范1.3.1部分,限定页面重定向或引入资源的目标地址。

// good: setAttribute操作前,限定引入资源的目标地址
function addExternalCss(e) {
	const t = document.createElement('link');
	t.setAttribute('href', e),
	t.setAttribute('rel', 'stylesheet'),
	t.setAttribute('type', 'text/css'),
	document.head.appendChild(t)
}

function validURL(sUrl) {
    return !!((/^(https?:\/\/)?[\w\-.]+\.(qq|tencent)\.com($|\/|\\)/i).test(sUrl) || (/^[\w][\w/.\-_%]+$/i).test(sUrl) || (/^[/\\][^/\\]/i).test(sUrl));
}

let sUrl = "https://evil.com/1.css"
if (validURL(sUrl)) {
	addExternalCss(sUrl);
}

 

1.2 流行框架/库的安全操作

1.2.1【必须】限定/过滤传入jQuery不安全函数的变量值

  • 使用.html().append().prepend().wrap().replaceWith().wrapAll().wrapInner().after().before()时,如变量值外部可控,应对特殊字符(&, <, >, ", ')做编码转义。
  • 引入jQuery 1.x(等于或低于1.12)、jQuery2.x(等于或低于2.2),且使用$()时,应优先考虑替换为最新版本。如一定需要使用,应对传入参数值中的特殊字符(&, <, >, ", ')做编码转义。
// bad:将不可信内容,带入jQuery不安全函数.after()操作
const { user } = params;
// ...
$("p").after(user);

// good: jQuery不安全函数.html()操作前,对特殊字符编码转义
function htmlEncode(iStr) {
	let sStr = iStr;
	sStr = sStr.replace(/&/g, "&amp;");
	sStr = sStr.replace(/>/g, "&gt;");
	sStr = sStr.replace(/</g, "&lt;");
	sStr = sStr.replace(/"/g, "&quot;");
	sStr = sStr.replace(/'/g, "&#39;");
	return sStr;
}

// const user = params.user;
user = htmlEncode(user);
// ...
$("p").html(user);
  • 使用.attr()操作a.hrefifame.srcform.actionembed.srcobject.datalink.hrefarea.hrefinput.formactionbutton.formaction属性时,应参考JavaScript页面类规范1.3.1部分,限定重定向的资源目标地址。

  • 使用.attr(attributeName, value)时,如第一个参数值attributeName外部可控,应用白名单限定允许操作的属性范围。

  • 使用$.getScript(url [, success ])时,如第一个参数值url外部可控(如:从URL取值拼接,请求jsonp接口),应限定可控变量值的字符集范围为:[a-zA-Z0-9_-]+

1.2.2【必须】限定/过滤传入Vue.js不安全函数的变量值

  • 使用v-html时,不允许对用户提供的内容使用HTML插值。如业务需要,应先对不可信内容做富文本过滤。
// bad:直接渲染外部传入的不可信内容
<div v-html="userProvidedHtml"></div>

// good:使用富文本过滤库处理不可信内容后渲染
<!-- 使用 -->
<div v-xss-html="{'mode': 'whitelist', dirty: html, options: options}" ></div>

<!-- 配置 -->
<script>
	new Vue({
	el: "#app",
	data: {
		options: {
			whiteList: {
				a: ["href", "title", "target", "class", "id"],
				div: ["class", "id"],
				span: ["class", "id"],
				img: ["src", "alt"],
			},
		},
	},
});
</script>
  • 使用v-bind操作a.hrefifame.srcform.actionembed.srcobject.datalink.hrefarea.hrefinput.formactionbutton.formaction时,应确保后端已参考JavaScript页面类规范1.3.1部分,限定了供前端调用的重定向目标地址。

  • 使用v-bind操作style属性时,应只允许外部控制特定、可控的CSS属性值

// bad:v-bind允许外部可控值,自定义CSS属性及数值
<a v-bind:href="sanitizedUrl" v-bind:style="userProvidedStyles">
click me
</a>

// good:v-bind只允许外部提供特性、可控的CSS属性值
<a v-bind:href="sanitizedUrl" v-bind:style="{
color: userProvidedColor,
background: userProvidedBackground
}" >
click me
</a>

 

1.3 页面重定向

1.3.1【必须】限定跳转目标地址

  • 使用白名单,限定重定向地址的协议前缀(默认只允许HTTP、HTTPS)、域名(默认只允许公司根域),或指定为固定值;

  • 适用场景包括,使用函数方法:location.hrefwindow.open()location.assign()location.replace();赋值或更新HTML属性:a.hrefifame.srcform.actionembed.srcobject.datalink.hrefarea.hrefinput.formactionbutton.formaction

// bad: 跳转至外部可控的不可信地址
const sTargetUrl = getURLParam("target");
location.replace(sTargetUrl);

// good: 白名单限定重定向地址
function validURL(sUrl) {
	return !!((/^(https?:\/\/)?[\w\-.]+\.(qq|tencent)\.com($|\/|\\)/i).test(sUrl) || (/^[\w][\w/.\-_%]+$/i).test(sUrl) || (/^[/\\][^/\\]/i).test(sUrl));
}

const sTargetUrl = getURLParam("target");
if (validURL(sTargetUrl)) {
	location.replace(sTargetUrl);
}

// good: 制定重定向地址为固定值
const sTargetUrl = "http://www.qq.com";
location.replace(sTargetUrl);

 

1.4 JSON解析/动态执行

1.4.1【必须】使用安全的JSON解析方式

  • 应使用JSON.parse()解析JSON字符串。低版本浏览器,应使用安全的Polyfill封装
// bad: 直接调用eval解析json
const sUserInput = getURLParam("json_val");
const jsonstr1 = `{"name":"a","company":"b","value":"${sUserInput}"}`;
const json1 = eval(`(${jsonstr1})`);

// good: 使用JSON.parse解析
const sUserInput = getURLParam("json_val");
JSON.parse(sUserInput, (k, v) => {
	if (k === "") return v;
	return v * 2;
});

// good: 低版本浏览器,使用安全的Polyfill封装(基于eval)
<script src="https://github.com/douglascrockford/JSON-js/blob/master/json2.js"></script>;
const sUserInput = getURLParam("json_val");
JSON.parse(sUserInput);

 

1.5 跨域通讯

1.5.1【必须】使用安全的前端跨域通信方式

  • 具有隔离登录态(如:p_skey)、涉及用户高敏感信息的业务(如:微信网页版、QQ空间、QQ邮箱、公众平台),禁止通过document.domain降域,实现前端跨域通讯,应使用postMessage替代。

1.5.2【必须】使用postMessage应限定Origin

  • 在message事件监听回调中,应先使用event.origin校验来源,再执行具体操作。

  • 校验来源时,应使用===判断,禁止使用indexOf()

// bad: 使用indexOf校验Origin值
window.addEventListener("message", (e) => {
	if (~e.origin.indexOf("https://a.qq.com")) {
	// ...
	} else {
	// ...
	}
});

// good: 使用postMessage时,限定Origin,且使用===判断
window.addEventListener("message", (e) => {
	if (e.origin === "https://a.qq.com") {
	// ...
	}
});

 

II. 配置&环境

 

2.1 敏感/配置信息

2.1.1【必须】禁止明文硬编码AK/SK

  • 禁止前端页面的JS明文硬编码AK/SK类密钥,应封装成后台接口,AK/SK保存在后端配置中心或密钥管理系统

 

2.2 第三方组件/资源

2.2.1【必须】使用可信范围内的统计组件

2.2.2 【必须】禁止引入非可信来源的第三方JS

 

2.3 纵深安全防护

2.3.1【推荐】部署CSP,并启用严格模式

 

Node.js后台类

 

I. 代码实现

 

1.1 输入验证

1.1.1【必须】按类型进行数据校验

  • 所有程序外部输入的参数值,应进行数据校验。校验内容包括但不限于:数据长度、数据范围、数据类型与格式。校验不通过,应拒绝。
// bad:未进行输入验证
Router.get("/vulxss", (req, res) => {
	const { txt } = req.query;
	res.set("Content-Type", "text/html");
	res.send({
		data: txt,
	});
});

// good:按数据类型,进行输入验证
const Router = require("express").Router();
const validator = require("validator");

Router.get("/email_with_validator", (req, res) => {
	const txt = req.query.txt || "";
	if (validator.isEmail(txt)) {
		res.send({
			data: txt,
		});
	} else {
		res.send({ err: 1 });
	}
});

关联漏洞:纵深防护措施 - 安全性增强特性

 

1.2 执行命令

1.2.1 【必须】使用child_process执行系统命令,应限定或校验命令和参数的内容

  • 适用场景包括:child_process.execchild_process.execSyncchild_process.spawnchild_process.spawnSyncchild_process.execFilechild_process.execFileSync

  • 调用上述函数,应首先考虑限定范围,供用户选择。

  • 使用child_process.execchild_process.execSync时,如果可枚举输入的参数内容或者格式,则应限定白名单。如果无法枚举命令或参数,则必须过滤或者转义指定符号,包括:|;&$()><`!

  • 使用child_process.spawn 或child_process.execFile时,应校验传入的命令和参数在可控列表内。

const Router = require("express").Router();
const validator = require("validator");
const { exec } = require('child_process');

// bad:未限定或过滤,直接执行命令
Router.get("/vul_cmd_inject", (req, res) => {
	const txt = req.query.txt || "echo 1";
	exec(txt, (err, stdout, stderr) => {
		if (err) { res.send({ err: 1 }) }
		res.send({stdout, stderr});
	});
});

// good:通过白名单,限定外部可执行命令范围
Router.get("/not_vul_cmd_inject", (req, res) => {
	const txt = req.query.txt || "echo 1";
  const phone = req.query.phone || "";
	const cmdList = {
    	sendmsg: "./sendmsg "
	};
	if (txt in cmdList && validator.isMobilePhone(phone)) {
        exec(cmdList[txt] + phone, (err, stdout, stderr) => {
          if (err) { res.send({ err: 1 }) };
          res.send({stdout, stderr});
        });
	} else {
		res.send({
			err: 1,
			tips: `you can use '${Object.keys(cmdList)}'`,
		});
	}
});

// good:执行命令前,过滤/转义指定符号
Router.get("/not_vul_cmd_inject", (req, res) => 

相关文章:

  • 2022-02-11
  • 2021-11-20
  • 2022-12-23
  • 2022-02-26
  • 2022-12-23
  • 2021-05-06
  • 2021-11-21
猜你喜欢
  • 2021-11-07
  • 2022-12-23
  • 2022-12-23
  • 2021-12-02
相关资源
相似解决方案