Comparing version 0.0.8 to 0.0.9
118
build/xss.js
@@ -1,2 +0,3 @@ | ||
;(function(e,t,n,r){function i(r){if(!n[r]){if(!t[r]){if(e)return e(r);throw new Error("Cannot find module '"+r+"'")}var s=n[r]={exports:{}};t[r][0](function(e){var n=t[r][1][e];return i(n?n:e)},s,s.exports)}return n[r].exports}for(var s=0;s<r.length;s++)i(r[s]);return i})(typeof require!=="undefined"&&require,{1:[function(require,module,exports){(function(){/** | ||
;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | ||
/** | ||
* 过滤XSS攻击 | ||
@@ -56,7 +57,11 @@ * | ||
var REGEXP_ATTR_NAME = /[^a-zA-Z0-9_:\.\-]/img; | ||
var REGEXP_ATTR_VALUE = /&#([a-zA-Z0-9]*);?/img; | ||
var REGEXP_ATTR_VALUE_1 = /&#([a-zA-Z0-9]*);?/img; | ||
var REGEXP_ATTR_VALUE_COLON = /:?/img; | ||
var REGEXP_ATTR_VALUE_NEWLINE = /&newline;?/img; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_1 = /\/\*|\*\//mg; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_2 = /^[\s"'`]*((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a):/ig; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_2 = /^[\s"'`]*((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a)\:/ig; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_3 = /\/\*|\*\//mg; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_4 = /((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a):/ig; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_4 = /((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a)\:/ig; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_5 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:/ig; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_6 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:\s*image\//ig; | ||
@@ -74,2 +79,4 @@ | ||
if (attr === 'href' || attr === 'src') { | ||
// 过滤 href 和 src 属性 | ||
// javascript: | ||
REGEXP_DEFAULT_ON_TAG_ATTR_1.lastIndex = 0; | ||
@@ -79,2 +86,3 @@ if (REGEXP_DEFAULT_ON_TAG_ATTR_1.test(value)) { | ||
} | ||
// /*注释*/ | ||
REGEXP_DEFAULT_ON_TAG_ATTR_2.lastIndex = 0; | ||
@@ -84,3 +92,14 @@ if (REGEXP_DEFAULT_ON_TAG_ATTR_2.test(value)) { | ||
} | ||
// data: | ||
REGEXP_DEFAULT_ON_TAG_ATTR_5.lastIndex = 0; | ||
if (REGEXP_DEFAULT_ON_TAG_ATTR_5.test(value)) { | ||
// 允许 data: image/* 类型 | ||
REGEXP_DEFAULT_ON_TAG_ATTR_6.lastIndex = 0; | ||
if (!REGEXP_DEFAULT_ON_TAG_ATTR_6.test(value)) { | ||
return '#'; | ||
} | ||
} | ||
} else if (attr === 'style') { | ||
// 过滤 style 属性 (这个xss漏洞较老了,可能已经不适用) | ||
// javascript: | ||
REGEXP_DEFAULT_ON_TAG_ATTR_3.lastIndex = 0; | ||
@@ -90,2 +109,3 @@ if (REGEXP_DEFAULT_ON_TAG_ATTR_3.test(value)) { | ||
} | ||
// /*注释*/ | ||
REGEXP_DEFAULT_ON_TAG_ATTR_4.lastIndex = 0; | ||
@@ -125,10 +145,76 @@ if (REGEXP_DEFAULT_ON_TAG_ATTR_4.test(value)) { | ||
/** | ||
* 过滤unicode字符(与REGEXP_ATTR_VALUE配合使用) | ||
* 清除不可见字符 | ||
* | ||
* @param {String} str | ||
* @return {String} | ||
*/ | ||
function replaceUnicode (str, code) { | ||
return String.fromCharCode(parseInt(code,10)); | ||
function clearNonPrintableCharacter (str) { | ||
var str2 = ''; | ||
for (var i = 0, len = str.length; i < len; i++) { | ||
str2 += str.charCodeAt(i) < 32 ? ' ' : str.charAt(i); | ||
} | ||
return str2.trim(); | ||
} | ||
/** | ||
* 对双引号进行转义 | ||
* | ||
* @param {String} str | ||
* @return {String} str | ||
*/ | ||
function escapeQuotes (str) { | ||
return str.replace(REGEXP_QUOTE, '"e;'); | ||
} | ||
/** | ||
* 对html实体编码进行转义 | ||
* | ||
* @param {String} str | ||
* @return {String} | ||
*/ | ||
function escapeHtmlEntities (str) { | ||
return str.replace(REGEXP_ATTR_VALUE_1, function replaceUnicode (str, code) { | ||
return (code[0] === 'x' || code[0] === 'X') | ||
? String.fromCharCode(parseInt(code.substr(1), 16)) | ||
: String.fromCharCode(parseInt(code, 10)); | ||
}); | ||
} | ||
/** | ||
* 对html5新增的危险实体编码进行转义 | ||
* | ||
* @param {String} str | ||
* @return {String} | ||
*/ | ||
function escapeDangerHtml5Entities (str) { | ||
return str.replace(REGEXP_ATTR_VALUE_COLON, ':') | ||
.replace(REGEXP_ATTR_VALUE_NEWLINE, ' '); | ||
} | ||
/** | ||
* 对属性值进行转义 | ||
* | ||
* @param {String} str | ||
* @return {String} | ||
*/ | ||
function safeAttrValue (str) { | ||
// 去掉两边的空白字符 | ||
str = str.trim(); | ||
// 过滤双引号 | ||
str = escapeQuotes(str); | ||
// 转换HTML实体编码 | ||
str = escapeHtmlEntities(str); | ||
// 转换危险的HTML5新增实体编码 | ||
str = escapeDangerHtml5Entities(str); | ||
// 清除不可见字符 | ||
str = clearNonPrintableCharacter(str); | ||
return str; | ||
} | ||
/** | ||
* XSS过滤对象 | ||
@@ -175,10 +261,5 @@ * | ||
if (value) { | ||
value = value.trim().replace(REGEXP_QUOTE, '"e;'); | ||
// 转换unicode字符 及过滤不可见字符 | ||
value = value.replace(REGEXP_ATTR_VALUE, replaceUnicode); | ||
var _value = ''; | ||
for (var i = 0, len = value.length; i < len; i++) { | ||
_value += value.charCodeAt(i) < 32 ? ' ' : value.charAt(i); | ||
} | ||
value = _value.trim(); | ||
// 先对属性值进行转义 | ||
value = safeAttrValue(value); | ||
// 使用用户自定义的 onTagAttr 再过滤 | ||
var newValue = me.onTagAttr(tagName, name, value); | ||
@@ -399,4 +480,4 @@ if (typeof newValue !== 'undefined') { | ||
})() | ||
},{"./utils":2}],2:[function(require,module,exports){/** | ||
},{"./utils":2}],2:[function(require,module,exports){ | ||
/** | ||
* 工具函数 | ||
@@ -451,2 +532,3 @@ * | ||
},{}]},{},[1]); | ||
},{}]},{},[1]) | ||
; |
108
lib/index.js
@@ -56,7 +56,11 @@ /** | ||
var REGEXP_ATTR_NAME = /[^a-zA-Z0-9_:\.\-]/img; | ||
var REGEXP_ATTR_VALUE = /&#([a-zA-Z0-9]*);?/img; | ||
var REGEXP_ATTR_VALUE_1 = /&#([a-zA-Z0-9]*);?/img; | ||
var REGEXP_ATTR_VALUE_COLON = /:?/img; | ||
var REGEXP_ATTR_VALUE_NEWLINE = /&newline;?/img; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_1 = /\/\*|\*\//mg; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_2 = /^[\s"'`]*((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a):/ig; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_2 = /^[\s"'`]*((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a)\:/ig; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_3 = /\/\*|\*\//mg; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_4 = /((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a):/ig; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_4 = /((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a)\:/ig; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_5 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:/ig; | ||
var REGEXP_DEFAULT_ON_TAG_ATTR_6 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:\s*image\//ig; | ||
@@ -74,2 +78,4 @@ | ||
if (attr === 'href' || attr === 'src') { | ||
// 过滤 href 和 src 属性 | ||
// javascript: | ||
REGEXP_DEFAULT_ON_TAG_ATTR_1.lastIndex = 0; | ||
@@ -79,2 +85,3 @@ if (REGEXP_DEFAULT_ON_TAG_ATTR_1.test(value)) { | ||
} | ||
// /*注释*/ | ||
REGEXP_DEFAULT_ON_TAG_ATTR_2.lastIndex = 0; | ||
@@ -84,3 +91,14 @@ if (REGEXP_DEFAULT_ON_TAG_ATTR_2.test(value)) { | ||
} | ||
// data: | ||
REGEXP_DEFAULT_ON_TAG_ATTR_5.lastIndex = 0; | ||
if (REGEXP_DEFAULT_ON_TAG_ATTR_5.test(value)) { | ||
// 允许 data: image/* 类型 | ||
REGEXP_DEFAULT_ON_TAG_ATTR_6.lastIndex = 0; | ||
if (!REGEXP_DEFAULT_ON_TAG_ATTR_6.test(value)) { | ||
return '#'; | ||
} | ||
} | ||
} else if (attr === 'style') { | ||
// 过滤 style 属性 (这个xss漏洞较老了,可能已经不适用) | ||
// javascript: | ||
REGEXP_DEFAULT_ON_TAG_ATTR_3.lastIndex = 0; | ||
@@ -90,2 +108,3 @@ if (REGEXP_DEFAULT_ON_TAG_ATTR_3.test(value)) { | ||
} | ||
// /*注释*/ | ||
REGEXP_DEFAULT_ON_TAG_ATTR_4.lastIndex = 0; | ||
@@ -125,10 +144,76 @@ if (REGEXP_DEFAULT_ON_TAG_ATTR_4.test(value)) { | ||
/** | ||
* 过滤unicode字符(与REGEXP_ATTR_VALUE配合使用) | ||
* 清除不可见字符 | ||
* | ||
* @param {String} str | ||
* @return {String} | ||
*/ | ||
function replaceUnicode (str, code) { | ||
return String.fromCharCode(parseInt(code,10)); | ||
function clearNonPrintableCharacter (str) { | ||
var str2 = ''; | ||
for (var i = 0, len = str.length; i < len; i++) { | ||
str2 += str.charCodeAt(i) < 32 ? ' ' : str.charAt(i); | ||
} | ||
return str2.trim(); | ||
} | ||
/** | ||
* 对双引号进行转义 | ||
* | ||
* @param {String} str | ||
* @return {String} str | ||
*/ | ||
function escapeQuotes (str) { | ||
return str.replace(REGEXP_QUOTE, '"e;'); | ||
} | ||
/** | ||
* 对html实体编码进行转义 | ||
* | ||
* @param {String} str | ||
* @return {String} | ||
*/ | ||
function escapeHtmlEntities (str) { | ||
return str.replace(REGEXP_ATTR_VALUE_1, function replaceUnicode (str, code) { | ||
return (code[0] === 'x' || code[0] === 'X') | ||
? String.fromCharCode(parseInt(code.substr(1), 16)) | ||
: String.fromCharCode(parseInt(code, 10)); | ||
}); | ||
} | ||
/** | ||
* 对html5新增的危险实体编码进行转义 | ||
* | ||
* @param {String} str | ||
* @return {String} | ||
*/ | ||
function escapeDangerHtml5Entities (str) { | ||
return str.replace(REGEXP_ATTR_VALUE_COLON, ':') | ||
.replace(REGEXP_ATTR_VALUE_NEWLINE, ' '); | ||
} | ||
/** | ||
* 对属性值进行转义 | ||
* | ||
* @param {String} str | ||
* @return {String} | ||
*/ | ||
function safeAttrValue (str) { | ||
// 去掉两边的空白字符 | ||
str = str.trim(); | ||
// 过滤双引号 | ||
str = escapeQuotes(str); | ||
// 转换HTML实体编码 | ||
str = escapeHtmlEntities(str); | ||
// 转换危险的HTML5新增实体编码 | ||
str = escapeDangerHtml5Entities(str); | ||
// 清除不可见字符 | ||
str = clearNonPrintableCharacter(str); | ||
return str; | ||
} | ||
/** | ||
* XSS过滤对象 | ||
@@ -175,10 +260,5 @@ * | ||
if (value) { | ||
value = value.trim().replace(REGEXP_QUOTE, '"e;'); | ||
// 转换unicode字符 及过滤不可见字符 | ||
value = value.replace(REGEXP_ATTR_VALUE, replaceUnicode); | ||
var _value = ''; | ||
for (var i = 0, len = value.length; i < len; i++) { | ||
_value += value.charCodeAt(i) < 32 ? ' ' : value.charAt(i); | ||
} | ||
value = _value.trim(); | ||
// 先对属性值进行转义 | ||
value = safeAttrValue(value); | ||
// 使用用户自定义的 onTagAttr 再过滤 | ||
var newValue = me.onTagAttr(tagName, name, value); | ||
@@ -185,0 +265,0 @@ if (typeof newValue !== 'undefined') { |
{ | ||
"name": "xss", | ||
"main": "./lib/index.js", | ||
"version": "0.0.8", | ||
"version": "0.0.9", | ||
"description": "XSS攻击代码过滤 Remove XSS attack vectors from user-supplied HTML", | ||
@@ -6,0 +6,0 @@ "author": "leizongmin <leizongmin@gmail.com> (http://ucdok.com)", |
@@ -6,2 +6,14 @@ [![Build Status](https://secure.travis-ci.org/leizongmin/js-xss.png?branch=master)](http://travis-ci.org/leizongmin/js-xss) | ||
![xss](https://nodei.co/npm/xss.png?downloads=true&stars=true) | ||
## 参考资料 | ||
+ [XSS与字符编码的那些事儿 ---科普文](http://drops.wooyun.org/tips/689) | ||
+ [腾讯实例教程:那些年我们一起学XSS](http://www.wooyun.org/whitehats/%E5%BF%83%E4%BC%A4%E7%9A%84%E7%98%A6%E5%AD%90) | ||
+ [XSS Filter Evasion Cheat Sheet](https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet) | ||
+ [Data URI scheme](http://en.wikipedia.org/wiki/Data_URI_scheme) | ||
+ [XSS with Data URI Scheme](http://hi.baidu.com/badzzzz/item/bdbafe83144619c199255f7b) | ||
## 安装 | ||
@@ -126,3 +138,3 @@ | ||
``` | ||
Copyright (c) 2012-2013 Lei Zongmin(雷宗民) <leizongmin@gmail.com> | ||
Copyright (c) 2012-2014 Zongmin Lei(雷宗民) <leizongmin@gmail.com> | ||
http://ucdok.com | ||
@@ -129,0 +141,0 @@ |
@@ -159,7 +159,7 @@ /** | ||
//assert.equal(xss('<IMG SRC=javascript:alert('XSS')>'), | ||
// '<img src="F M LEJN ALN !">'); | ||
assert.equal(xss('<IMG SRC=javascript:alert('XSS')>'), | ||
'<img src="#">'); | ||
assert.equal(xss('<IMG SRC=javascript:alert('XSS')>'), | ||
'<img src>'); | ||
'<img src="#">'); | ||
@@ -207,2 +207,5 @@ assert.equal(xss('<IMG SRC="jav ascript:alert(\'XSS\');">'), '<img src="#">'); | ||
assert.equal(xss('<a href="javascript">'), '<a href="javascript">'); | ||
assert.equal(xss('<a href="/javascript/a">'), '<a href="/javascript/a">'); | ||
// 这个暂时不知道怎么处理 | ||
@@ -214,4 +217,21 @@ //assert.equal(xss('¼script¾alert(¢XSS¢)¼/script¾'), ''); | ||
// HTML5新增实体编码 冒号: 换行
 | ||
assert.equal(xss('<a href="javascript:alert(/xss/)">'), '<a href="#">'); | ||
assert.equal(xss('<a href="javascript&colonalert(/xss/)">'), '<a href="#">'); | ||
assert.equal(xss('<a href="a
b">'), '<a href="a b">'); | ||
assert.equal(xss('<a href="a&NewLineb">'), '<a href="a b">'); | ||
assert.equal(xss('<a href="javasc
ript:alert(1)">'), '<a href="#">'); | ||
// data URI 协议过滤,只允许 data: image/* | ||
assert.equal(xss('<a href="data:">'), '<a href="#">'); | ||
assert.equal(xss('<a href="d a t a : ">'), '<a href="#">'); | ||
assert.equal(xss('<a href="data: html/text;">'), '<a href="#">'); | ||
assert.equal(xss('<a href="data:html/text;">'), '<a href="#">'); | ||
assert.equal(xss('<a href="data:html /text;">'), '<a href="#">'); | ||
assert.equal(xss('<a href="data: image/text;">'), '<a href="data: image/text;">'); | ||
assert.equal(xss('<img src="data: aaa/text;">'), '<img src="#">'); | ||
assert.equal(xss('<img src="data:image/png; base64; ofdkofiodiofl">'), '<img src="data:image/png; base64; ofdkofiodiofl">'); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
154893
1210
160
4