前端安全-XSS主动防御

通俗篇:

xss俗称跨域脚本攻击,是最常见的web攻击,其重点是跨域和客户端执行。目前手头开发的一个项目,被利用input提交<img οnerrοr="alert(123)">到后端,进而将其存储进数据库,导致再次读取显示该内容时出发alert执行。

遂~,上网找大家的解决办法,最常用的解决办法就是转移、过滤。

这里有一个比较通顺的xss存储攻击:基于存储的xss攻击,是通过发表带有恶意跨域脚本的帖子/文章,从而把恶意脚本存储在服务器,每次访问该帖子都会被执行。

具体解决方案:1、首先是服务端要进行过滤,因为前端的校验可以被绕过。

                        2、当服务端不校验的时候,前端要以各种方式过滤里面可能的恶意脚本,例如script,将特殊字符转换成HTML编码。

大神篇:

另外LZ还看了张云龙大神的相关文章,xss前端防火墙--内联事件拦截。其核心是通过前端监控脚本,让每个用户都参与漏洞的监控和 上报。

因为就xss防御本身,至今都没有一劳永逸的办法,大都处在被动防御的阶段,又因为被动防御中从问题出现到被发现往往要经过很长一段时间,所以张大大对xss的改进重点在于缩短漏洞被发现的时间,最大程度上降低损失。

解释完精髓之后,来谈谈大神具体的操作:

被动扫描:

最简单办法就是把页面所有元素都扫描一边,检测那些on开头的内联属性,看看是不是存在异常:

例如字符数非常多,正常情况下这是很少出现的,但xss为了躲避转义有时会编码的很长;例如出现一些xss经常使用的关键字,但在实际产品中几乎不会用到的,这些都可以作为漏洞出现的征兆,通知给开发人员。

不过,土办法终究存在很大的局限性,在如今清一色的Ajax时代,页面元素从来都不是固定的。伴随着用户各种交互,新内容随时都可能动态被添加进来。

如同早期的安全软件一样,每隔几秒扫描一次注册表启动项,不仅浪费性能,而且对恶意软件几乎不起作用,

但之后的主动防御系统就不同了,之后在正真调用API时才进行分析,不通过则直接拦截,完全避免的定时器的间隔遗漏。

因此我们需要类似的延时策略,仅在xss即将触发时对其分析,对不符合策略的元素,进行拦截或者放行,同时发送报警到后台日志。

主动防御:

主动防御放在前端脚本里面似乎有些玄乎,但不难发现,仅仅是执行优先级的事而已,只要防御程序能运行在其他程序之前,我们就有了可进可退的主动权,对于无比强大的HTML和灵活多变的JavaScript,这些概念都可以被玩转出来。

继续回到刚才套路的内联事件xss上来,浏览器虽然没提供可操作内联事件的接口,但内联事件的本质仍是一个事件,无论怎么变化都离不开DOM事件模型。

扯到模型上面,一切将引刃而解,模型是解决问题最靠谱的办法,尤其是像DOM-3-Event这种早已制定的模型,其稳定性,毋容置疑。

即便没仔细阅读过官方文档,但凡做过网页都知道,有个addEventListener的接口,并取代了一个曾经古老的叫attachEvent的东西。尽管只是新增了一个参数而已,但正是这个差别成了人们津津乐道的话题。每当面试的时候总少不了考察这个新参数的用途,尽管日常开发很少用到。

关于事件捕获个冒泡的细节都不多讨论了,下面这段代码也许能激发你对主动防御的遐想:

<button onclick="console.log('target')">CLICK ME</button>
<script>
	document.addEventListener('click', function(e) {
		console.log('bubble');
	});

	document.addEventListener('click', function(e) {
		console.log('capture');
		//e.stopImmediatePropagation();
	}, true);
</script>

run:

尽管按钮上直接绑定了一个内联的事件,但事件模型并不买账,仍然的按照标准的模型走一遍。capture target bubble,模型就是那样固执。

不过把那行注释代码回复,结果就只剩capture了。这个简单的道理大家都明白也没什么好解释的。

但仔细揣摩下,这不就是【主动防御】的概念吗,捕获程序运行在内联事件触发之前,并且有能力拦截之后的调用。

上面的Demo只是不假思索拦截了所有事件。如果我们再添一些策略判断,或许就更明朗了。

<button onclick="console.log('xss')">CLICK ME</button>
<script>
	document.addEventListener('click', function(e) {
		console.log('bubble');
	});

	document.addEventListener('click', function(e) {
		var element = e.target;
		var code = element.getAttribute('onclick');

		if (/xss/.test(code)) {
			e.stopImmediatePropagation();
			alert('拦截可疑事件: ' + code);
		}
	}, true);
</script>

Run:

我们现在捕获阶段扫描内联事件字符,若是出现了[xss]这个关键字,后续的事件就被拦截了;换成其他字符,仍然继续执行。同理,我们还可以判断字符长度是否过多,以及更详细的黑白名单正则。

怎么样,一个主动防御的原型诞生了。

不过上面的片段还有小问题,就是把事件的冒泡过程也给屏蔽了,而我们仅仅想拦截内联事件而已。解决办法也很简单,把e.stopImmediatePropagation()换成element.οnclick=null; 就可以了。

当然目前这只能防护onclick,而现实中有太多的内联事件,鼠标 键盘 触屏 网络状态等等,不同浏览器支持的事件也不一样,甚至还有私有事件,难道都要事先逐一列出并且都捕获吗?是的,但不必事先都列出来。

因为我们监听的document对象,浏览器所有的内联事件都对应着document.on***这类属性,因此只需运行时遍历一下document对象,即可获得所有事件名。

<img src="*" onerror="alert('xss')" />
<script>
	function hookEvent(eventName) {
		document.addEventListener(eventName.substr(2), function(e) {
			var element = e.target;
			if (element.nodeType != Node.ELEMENT_NODE) {
				return;
			}
			var code = element.getAttribute(eventName);
			if (code && /xss/.test(code)) {
				element[eventName] = null;
				alert('拦截可疑事件:', code);
			}
		}, true);
	}

	console.time('耗时');
	for (var k in document) {
		if (/^on./.test(k)) {
			//console.log('监控:', k);
			hookEvent(k);
		}
	}
	console.timeEnd('耗时');
</script>

run :

现在,无论页面中哪个元素触发哪个内联事件,都能预先被我们捕获,并根据策略可进可退了。(不过在 OSX 10.9+ 的 Safari 浏览器无法枚举出 on 开头的属性,可能是个 BUG 吧~)


其他内联事件:

现实中,除了以on开头这种内联外,还存在一些特殊形式。最常见的就是javascript:的属性,在一些历史遗留的非标准浏览器中,饱受诟病。

例如曾经的IE浏览器就支持这样的URL:

<img src="javascript:alert('hello')">
<img src='vbscript:msgbox "hello"'>

这种画蛇添足的设计,曾导致过去无数的论坛深受其害。

如今这种毫无意义的过度设计早已被标准抛弃,除了某些小众浏览器或许仍有遗留。

不过,有一个使用特别广泛,以至如今标准仍有保留,那就是:

<a href="javascript:">

对于这类情况,我们就得单独对待,做特殊处理:

<a href="javascript:alert(/xss/)">Click Me</a>
<script>
	function hookEvent(eventName) {
		var isClick = (eventName == 'onclick');

		document.addEventListener(eventName.substr(2), function(e) {
			var el = e.target;
			if (el.nodeType != Node.ELEMENT_NODE) {
				return;
			}

			// ...

			// 扫描 <a href="javascript:"> 的脚本
			if (isClick && el.tagName == 'A' && el.protocol == 'javascript:') {
				var code = el.href.substr(11);
				if (/xss/.test(code)) {
					el.href = 'javascript:void(0)';
					alert('拦截可疑事件:', code);
				}
			}
		}, true);
	}

	for (var k in document) {
		if (/^on./.test(k)) {
			hookEvent(k);
		}
	}
</script>

性能优化:

或许有些事件没有必要捕获,例如视频播放、音量调节等。但就算全都捕获也消耗不了多少时间,基本都在1ms左右。

当然注册事件本身就花不了多少时间,真正的耗费都算在回掉上了。尽管大多数事件触发都不频繁,额外的扫描可以忽略不计。但和鼠标移动相关的事件那就不容忽视了,因此的考虑性能优化。

显然,内联事件代码在运行过程中几乎不可能发生改变。使用内联事件大多为了简单,如果还要在运行时setAttribute去改变内联代码,完全就是不可理喻的。因此,我们只需对某个元素的特定事件扫描一次就可以了。之后根据标记,即可直接跳过。

<div style="width:100%; height:100%; position:absolute; background: red" onmousemove="alert('xss')">
	<a href="javascript:alert(/xss/)">Click Me</a>
</div>
<script>
	var mCheckMap = {};
	var mCheckID = 0;

	function hookEvent(eventName, eventID) {
		var isClick = (eventName == 'onclick');

		function scanElement(el) {
			//
			// 跳过已扫描的事件
			//
			var flag = el['_k'];
			if (!flag) {
				flag = el['_k'] = ++mCheckID;
			}

			var hash = (flag << 8) | eventID;
			if (hash in mCheckMap) {
				return;
			}
			mCheckMap[hash] = true;

			// 非元素节点
			if (el.nodeType != Node.ELEMENT_NODE) {
				return;
			}

			// 扫描内联代码
			var code;
			if (el[eventName]) {
				code = el.getAttribute(eventName);
				if (code && /xss/.test(code)) {
					el[eventName] = null;
					alert('拦截可疑事件:' + code);
				}
			}

			// 扫描 <a href="javascript:"> 的脚本
			if (isClick && el.tagName == 'A' && el.protocol == 'javascript:') {
				var code = el.href.substr(11);
				if (/xss/.test(code)) {
					el.href = 'javascript:void(0)';
					alert('拦截可疑事件:' + code);
				}
			}

			// 扫描上级元素
			scanElement(el.parentNode);
		}

		document.addEventListener(eventName.substr(2), function(e) {
			scanElement(e.target);
		}, true);
	}

	var i = 0;
	for (var k in document) {
		if (/^on./.test(k)) {
			hookEvent(k, i++);
		}
	}
</script>

run:

这样,之后的扫描仅仅时检测一下,目标对象是否存在标记而已,即使疯狂晃动鼠标,cpu使用率也可以忽略不计了。

与之前不同的是,这里我们增加了一个叫scanElement的函数,并递归扫描上级元素。之所以这么做,还是因为和冒泡有关。即使当前元素不存在内联事件,但并不代表上级容器也没有。因此,我们将元素,自身及所有上级DOM都扫描一遍,以防万一,由于扫描过的会由标记,所以并不会增加性能消耗。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、什么是XSS攻击 XSS是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。比如这些代码包括HTML代码和客户端脚本。攻击者利用XSS漏洞旁路掉访问控制——例如同源策略(same origin policy)。这种类型的漏洞由于被黑客用来编写危害性更大的网络钓鱼(Phishing)攻击而变得广为人知。对于跨站脚本攻击,黑客界共识是:跨站脚本攻击是新型的“缓冲区溢出攻击“,而JavaScript是新型的“ShellCode”。 二、XSS漏洞的危害 (1)网络钓鱼,包括盗取各类用户账号; (2)窃取用户cookies资料,从而获取用户隐私信息,或利用用户身份进一步对网站执行操作; (3)劫持用户(浏览器)会话,从而执行任意操作,例如进行非法转账、强制发表日志、发送电子邮件等; (4)强制弹出广告页面、刷流量等; (5)网页挂马; (6)进行恶意操作,例如任意篡改页面信息、删除文章等; (7)进行大量的客户端攻击,如DDoS攻击; (8)获取客户端信息,例如用户的浏览历史、真实IP、开放端口等; (9)控制受害者机器向其他网站发起攻击; (10)结合其他漏洞,如CSRF漏洞,实施进一步作恶; (11)提升用户权限,包括进一步渗透网站; (12)传播跨站脚本蠕虫等; 三、过滤器配置 web.xml配置 XssFilter com.xxx.Filter.XssFilter XssFilter /*

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值