非字母数字的JS(二)
在2011年的BlackHat DC 2011大会上Ryan Barnett给出了一段关于XSS的示例javascript代码:
上面这段代码在浏览器中输出alert(1),这段代码中巧妙利用sort函数执行返回window对象来获取window对象,然后执行window['alert'](1)。关于这段代码的详细解释可查看这里。
关于上面代码的实现技术原理,可以查看《非字母数字组成的JavaScript》。
由上面的代码,引发思考能不能输出数字(0-9)和全部26个字母,于是进行了下面尝试:
console.log(+[]); //0
console.log(-~[]); //1
console.log(-~-~[]); //2
console.log(-~-~-~[]); //3
console.log(-~-~-~-~[]); //4
console.log(-~-~-~-~-~[]); //5
console.log(-~-~-~-~-~-~[]); //6
console.log(-~-~-~-~-~-~-~[]); //7
console.log(-~-~-~-~-~-~-~-~[]); //8
console.log(-~-~-~-~-~-~-~-~-~[]); //9
通过上面这种方法,可以得到0-9,再组合0-9就可以得到任意数值了。
//[{}]+[] [object Object]
console.log(([{}]+[])[-~[]]); //o
console.log(([{}]+[])[-~-~[]]); //b
console.log(([{}]+[])[-~-~-~[]]); //j
console.log(([{}]+[])[-~-~-~-~[]]); //e
console.log(([{}]+[])[-~-~-~-~-~[]]); //c
console.log(([{}]+[])[-~-~-~-~-~-~[]]); //t
//![]+[] false
console.log((![] + [])[+[]]); //f
console.log((![] + [])[-~[]]); //a
console.log((![] + [])[-~-~[]]); //l
console.log((![] + [])[-~-~-~[]]); //s
console.log((![] + [])[-~-~-~-~[]]); //e
//!![]+[] true
console.log((!![]+[])[+[]]); //t
console.log((!![]+[])[-~[]]); //r
console.log((!![]+[])[-~-~[]]); //u
console.log((!![]+[])[-~-~-~[]]); //e
//({}[(!![]+[])[+[]]])+[] undefined
console.log((({}[(!![]+[])[+[]]])+[])[-~[]]); //n
console.log((({}[(!![]+[])[+[]]])+[])[-~-~[]]); //d
console.log((({}[(!![]+[])[+[]]])+[])[-~-~-~-~-~[]]); //i
经过尝试,暂时只能得到a b c d e f i j l n o r s t u共15个字母。欢迎补充
轻量级模板系统
开发问卷系统时,开发了一个简单的模板管理功能。实现了简单的变量替换、循环、自定义函数、并提供了循环变量tmCount(从0开始)和tmNumber(从1开始)。不多说,先上代码:
var templates = {},
tmp,
supplant = {
init: function (str, params) {
var value, prop, tmp, len;
for (prop in params) {
if (params.hasOwnProperty(prop)) {
value = params[prop];
switch (Object.prototype.toString.call(value)) {
case '[object Number]':
case '[object String]':
tmp = {};
tmp[prop] = value;
str = supplant.replaceString(str, tmp);
break;
case '[object Function]':
str = supplant.replaceFunc(str, prop, value, params);
break;
case '[object Array]':
str = supplant.replaceArray(str, prop, value);
break;
default:
break;
}
}
}
return str;
},
replaceString: function (str, params) {
var prop;
for (prop in params) {
if (params.hasOwnProperty(prop)) {
str = str.split('{' + prop + '}').join(params[prop]);
}
}
return str;
},
replaceFunc: function (str, prop, func, item) {
return str.split('{' + prop + '}').join(func(item));
},
replaceArray: function (str, prop, data) {
var i = 0,
left,
middle,
right,
s = '{#' + prop + '}',
e = '{/' + prop + '}',
len = data.length,
start = 0,
end = 0;
start = str.indexOf(s);
end = str.indexOf(e, start);
left = str.slice(0, start);
middle = str.slice(start + s.length, end);
right = str.slice(end + e.length);
if (len > 0 && start > -1) {
for (; i < len; ++i) {
data[i].tmCount = i;
data[i].tmNumber = i + 1;
if (data[i].content !== null) { //如果content属性不等于null
left += supplant.init(middle, data[i]);
} else {
left += data[i].replace;
}
}
str = left + right;
if (str.indexOf(s) > -1) {//如果包括多个循环
str = supplant.replaceArray(str, prop, data);
}
}
if (len == 0 && start > -1) {
str = left + right;
}
return str;
}
};
return {
render: function (name, params) {
if (typeof templates[name] !== 'string') {
throw 'Template ' + name + ' not found!';
}
tmp = name;
return supplant.init(templates[name], params);
},
defineTemplate: function (name, template) {
if (typeof template == 'string') {
templates[name] = template;
} else { //数组
templates[name] = template.join('');
}
}
};
})();
上面代码中定义一个templateManager对象用来管理模板,提供了两个接口:
- defineTemplate 用来定义模板
- render 用来渲染模板
内部私有的supplant对象实现模板引擎,提供了替换变量、替换循环、替换函数的功能。
文档和示例:
templateManager.defineTemplate('profile', '姓名:{name},blog:{blog}');
templateManager.render('profile', {name: 'vapour', blog: 'http://dovapour.info/'});
//结果 姓名:vapour,blog:http://dovapour.info/
//实现循环
templateManager.defineTemplate('website', [
'<ul>',
'{#webs}<li><a href="{url}">{name}<_buff_29_buff_li>{/webs}',
'<ul>'
]);
templateManager.render('website', {
webs: [{
name: '百度',
url: 'http://www.baidu.com/',
getLen: function (item) {
return item.url.length;
}
}, {
name: '淘宝',
url: 'http://www.taobao.com/',
getLen: function (item) {
return item.url.length;
}
}, {
name: '腾讯',
url: 'http://www.qq.com/',
getLen: function (item) {
return item.url.length;
}
}, {
name: '网易',
url: 'http://163.com/',
getLen: function (item) {
return item.url.length;
}
}, {
name: '新浪',
url: 'http://www.sina.com/',
getLen: function (item) {
return item.url.length;
}
}]
});
/*
<ul>
<li><a href="http://www.baidu.com/">百度</a>,url长度21</li>
<li><a href="http://www.taobao.com/">淘宝</a>,url长度22</li>
<li><a href="http://www.qq.com/">腾讯</a>,url长度18</li>
<li><a href="http://163.com/">网易</a>,url长度15</li>
<li><a href="http://www.sina.com/">新浪</a>,url长度20</li>
<ul>
*/
ie中的跨域和ckeditor
在IE中修改了document.domain进行提权后(无论修改前后域名是否不同),iframe都会出现跨域问题。
ckeditor中检查跨域的代码,是根据判断document.domain和window.location.hostname是否相同来来判断跨域的。
if (!this.ie) return false;
var g = document.domain,
h = window.location.hostname;
return g != h && g != '[' + h + ']';
}
当我们执行在页面执行 document.domain = document.domain 后,执行 isCustomDomain 会返回false,这时ie会因为跨域,报拒绝访问的错误。
所以当我们,在修改document.domain时要进行判断,当新域和旧域不同时再修改document.domain
newDomain = 'xxx.com';
if (newDomain != oldDomain) {
document.domain = newDomain;
}
跨浏览器实现全选功能
跨浏览器实现全选功能,IE一行代码就OK,标准浏览器就比较麻烦了,需要使用selection、range、addRange实现。
if (arale.isIE()) { //IE
return function (win) {
win = win || window;
win.document.body.createControlRange().execCommand('SelectAll');
}
} else { //标准浏览器
return function (win) {
var s, r;
win = win || window;
doc = win.document;
s = win.getSelection();
r = doc.createRange();
r.selectNode(doc.body);
s.addRange(r);
}
}
})();
使用动态类型、对象构造而非继承
今天读了OOP The Good Parts: Message Passing, Duck Typing, Object Composition, and not Inheritance,作者提倡使用动态类型(duck typing)、对象构造来代替继承。
关于动态类型的示例代码:
if ( ! (foo instanceof Bar) ) {
throw new TypeError("This is an annoying restriction!");
}
return foo.baz(foo.quux(10));
}
function good (foo) {
if ( !foo.baz || !foo.quux ) {
throw new TypeError("We need foo to have baz and quux methods.");
}
return foo.baz(foo.quux(10));
}
下面是作者给出使用动态类型和对象构造的一个操作DOM的示例:
var k, ctor;
for ( k in definition ) {
ctor = definition[k];
if ( ctor === Number ) {
if ( Object.prototype.toString.call(obj[k]) !== "[object Number]" || isNaN(obj[k]) ) {
return false;
}
} else if ( ctor === String ) {
if ( Object.prototype.toString.call(obj[k]) !== "[object String]" ) {
return false;
}
} else if ( ! (obj[k] instanceof ctor) ) {
return false;
}
}
return true;
};
Object.combine = function () {
var newObj = {},
i = 0,
args = Array.prototype.slice.call(arguments),
len = args.length;
function copyProperty(k) {
newObj[k] = args[i][k];
}
for ( ; i < len; i++ ) {
Object.getOwnPropertyNames(args[i]).forEach(copyProperty);
}
return newObj;
};
window.$ = (function () {
var undef, listen, customAPI;
// Private function to turn 'thing' in to an array,
// optionally slicing the first 'n' elems off.
function toArray(thing, n) {
return Array.prototype.slice.call(thing, n || 0);
}
// Private function to partially apply the first n arguments
// to 'fn'.
function curry (fn) {
var args = toArray(arguments, 1);
return function () {
return fn.apply(this, args.concat(toArray(arguments)));
};
}
// Private function to set the attribute 'attr' of a single
// DOM node 'node' to 'val'.
function nodeAttributeSetter (attr, val, node) {
node.setAttribute(attr, val);
}
// Private function to get the value of the attribute 'attr'
// on the DOM node 'node'.
function nodeAttributeGetter (attr, node) {
return node.getAttribute(attr);
}
// Do the work to determine the correct method for attaching
// event listeners only once by using 'quacksLike' and an
// immediately invoked function.
listen = (function () {
var w3c = {
addEventListener: Function
},
ie = {
attachEvent: Function
};
if ( quacksLike(document.body, w3c) ) {
return function (event, fn, node) {
node.addEventListener(event, fn, false);
};
} else if ( quacksLike(document.body, ie) ) {
return function (event, fn, node) {
node.attachEvent("on" + event, fn);
};
} else {
throw new TypeError("Do not know how to attach event listeners.");
}
}());
// This object is a mixin to be composed with the NodeList
// results of querying the DOM by CSS selector. It provides
// handy methods for getting and setting attributes,
// attaching event listeners, and performing sub-queries by
// CSS on these elements.
customAPI = {
attr: function (attr, val) {
if ( val !== undef ) {
this.forEach(curry(nodeAttributeSetter, attr, val));
return this;
} else {
return this.length > 0 ? nodeAttributeGetter(attr, this[0]) : "";
}
},
on: function (event, fn) {
this.forEach(curry(listen, event, fn));
return this;
},
find: function (selector) {
var res = [];
this.forEach(function (node) {
res.push.apply(res, toArray(node.querySelectorAll(selector)));
});
return Object.combine(res, customAPI);
}
};
return function (selector, context) {
context = context || document;
return Object.combine(Array.prototype, context.querySelectorAll(selector), customAPI);
};
}());
//The mini DOM library might be used like this:
// The following two forms are equivalent.
// They select the same elements.
$("div").find("p");
$("div p");
// Disable all links.
$("a").on("click", function (event) {
event.preventDefault();
alert("Don't leave me all alone!");
});
// Get the id of the first paragraph.
$("p").attr("id");
// Disable all input elementss.
$(input").attr("disabled", "disabled");
// Do stuff with each matched element.
$("div.foo").forEach(function (el) {
doStuffWith(el);
});
CSS3动画实现mouseLeave和mouseEnter的不同效果
Eric在Inconsistent Transitions 中指出CSS3动画在浏览器中的不一致。
transition: 1s transform;
transform: rotate(270deg);
}
上面代码只在鼠标移动到div上时有过渡动画(transition),在鼠标离开div时没有过渡动画。并给出如下解决文案:
transition: 1s transform;
}
div:hover span {
transform: rotate(270deg);
}
这样,当鼠标进入和离开div时,都有过渡动画效果。其实我们可以进一步实现鼠标进入(mouseEnter)和鼠标离开(mouseLeave)的不同效果。
div span {
transition: 3s transform;
}
/*mouseenter*/
div:hover span {
transform: rotate(270deg);
transition: 1s transform;
}
这样,就实现了mouseLeave和mouseEnter的不同过渡效果。
label导致冒泡事件的诡异问题
今天遇到一个很怪异的问题,给label标签绑定了click事件,但是每次单击时,click事件都会执行两次。开始以为自己的代码有问题,反复痛苦的检查了N久以后,才发现自己代码没问题。根本原因在于事件的冒泡机制和label标签共同作用引起的。
<li><label for="q1"><input id="q1" type="text" value="" />工商</label></li>
<li><label for="q2"><input id="q2" type="text" value="" />农业</label></li>
<li><label for="q3"><input id="q3" type="text" value="" />招商</label></li>
<li><label for="q4"><input id="q4" type="text" value="" />建设</label></li>
<li><label for="q5"><input id="q5" type="text" value="" />浦发</label></li>
</ul>
执行两次的过程如下:
- 单击label标签,第一次执行click事件
- 由于label标签的特殊性,事件向下传递到input标签
- 事件开始冒泡几上传递
- 冒泡到label标签时,第二次执行click事件
为了更便于说明上面的过程,请看下面的示例:
<input id="txtUser" type="text" value="单击这里alert二次" />
单击这里alert三次
</label>
<script type="text/javascript">
$('lbUser').click(function(){
alert('单击label');
});
$('txtUser').click(function(){
alert('单击input');
});
</script>
当点击label中的文字时,依次弹出:
- alert('单击label')
- alert('单击input');
- alert('单击label');
当直接点击input输入框时,依次弹出:
- alert('单击input');
- alert('单击label');
总结:这个问题让自己纠结了,很长时间。主要原因时没有考虑到冒泡,需要注意的这里我们注册的事件都是绑定在冒泡期间,但是因为label(会自动将焦点转到和标签相关的表单控件上),所以导致了这个意外。
非字母数字的JavaScript
由非字母数字组成的JavaScript,这是几年前一个日本安全专家Yosuke Hasegawa在slackers论坛上提出的。这种技术主要是利用JavaScript的数据类型自动转换的特性,例如布尔类型true、false可以转换成字符类型"true"、"false"。
现在我们利用上面这种技术来获取字母o,我们可以利用对象调用toString方法转换成字符串[object Object]来得到字母o。
1 首页我们需要获得对象字面量[object Object]:
2 得到数字0
3 得到数字1,在0的基础上增加1
4最后合并上面代码,得到字母o
如果对这方面感兴趣,可以阅读:
YAUC Less chars needed to run arbitrary JS code = 6! (JS GREAT WALL)
http://sla.ckers.org/forum/
Diminuitive NonAlNum JS - Arbitrary:-
http://sla.ckers.org/forum/
Java/script: no alnum cheat sheets:-
http://sla.ckers.org/forum/read.php?24,33349
Diminutive JS Code Challenge, from OWASP :-
http://sla.ckers.org/forum/read.php?24,30015
数组的小技巧
JavaScript array “extras” in detail 介绍了ES5对数组原型对象扩充的方法:forEach、map、filter、some、every、indexOf、lastIndexOf、reduce、reduceRight。这些方法不仅可以应用到数组,还可应用到拥有length属性,并且可以数字索引的对象上,例如String,arguments,DOM nodes collection等。
var map = Array.prototype.map;
// and call it for a string
var hello = map.call("hello world", function (char) {
return char + "*";
});
alert(hello.join("")); // "h*e*l*l*o* *w*o*r*l*d*"
除了上面这些ES5的方法,join方法也可应用到拥有length属性,并且可以数字索引的对象上,示例:
f = Array.prototype.join;
var str = f.call(s, '-');
alert(str);// a-b-c-d
文中还有一个数组方法重用字符串方法的例子:
var toUpperCase = String.prototype.toUpperCase;
var upper = toUpperCase.apply(["foo", "bar"]).split(",");
alert(upper); // ["FOO", "BAR"]
下面是John Resig 给出的获取数组最大与最小值的方法,很有创意:
return Math.min.apply( Math, array );
}
function largest(array){
return Math.max.apply( Math, array );
}
关于typeof运算
在JavaScript中,变量可以不声明直接赋值使用,但不能直接引用,否则会出错,看下面代码:
if(foo){ //reference error
alert('true');
}
typeof(foo); //undefined
看了上面代码对于进行typeof运算为什么不报错,而是返回undefined,这是因为typeof操作并不调用GetValue。ECMA中关于typeof操作的描述:
The production UnaryExpression : typeof UnaryExpression is evaluated as follows:
- Let val be the result of evaluating UnaryExpression
- If Type(val) is Reference, then
- If IsUnresolvableReference(val) is true, return "undefined"
- Let val be GetValue(val)
- Return a String determined by Type(val) according to Table
| typeof val | result |
| undefined | "undefined" |
| Null | "object" |
| Boolean | "boolean" |
| Number | "number" |
| String | "string" |
| Object (native and does not implement [[Call]]) | "object" |
| Object (native or host and does implement [[Call]]) | "function" |
| Object (host and does not implement [[Call]]) |
Implementation-defined except may not be "undefined", "boolean", "number", or "string". |