WEB安全第八课 浏览器端脚本 之三 标准对象层级 |
JavaScript的运行环境都是围绕一个隐含的根对象构建出来的,这个根对象也是JavaScript程序里所有全局变量和函数的默认命名空间。
除了若干JavaScript语言的内置功能,这个命名空间里还预设了一些层级模式的函数,用于实现浏览器环境里的输人输出功能。这些功能包括:对浏览器窗口的操作(open(...)、close()、moveTo(...)、resizeTo(...)、focus()和blur()等);JavaScript定时器设置(setTimeout(...)、setInterval(...)等);各种UI提示(alert(...)、prompt(...)、print(...));还有各种与浏览器开发商相关但有风险的函数,如访问系统剪贴板、创建书签或改变默认首页等。 ![]() 这个顶级对象还提供了其他相关上下文环境的根对象的JavaScript引用,如父框架(parent),当前浏览器窗口里的顶层文档(top),创建出当前窗口的源窗口(opener)和当前文档里的所有子框架(frames[])。其他对当前根对象自身的循环引用还包括window和self。除Firefox外,对设定过id或name参数的元素也会在此空间里自动注册,语法如下: [mw_shl_code=javascript,true] <img id="he11o" src="http://www.examp1e.com/"> ... <script> 🖕🎢🍚⚛🐠 alert(hello.src);</script>[/mw_shl_code] 谢天谢地,当这些名称与代码里的变量或JavaScript内置变量有冲突时,这些id数据的优先级会低于内置变量,这在很大程度上避免了由于用户提供的内容里有非法内容,而干扰到文档脚本自身的参数和变量。 👊🚐🍓♂🦖 最顶级的对象结构里,除了Document对象以外,还包括若干主要的子对象,它们组成了浏览器的各个API特性: location对象:这个对象包括读取当前文档URL信息或转向新地址浏览的各种属性和方法。在大多数情况下,新地址跳转对于发起方来说可能是致命的:因为当前的脚本上下文环境会被完全销毁,并迅速切换到新的运行环境里。但是如果只改变片段ID(location.hash)则不在此列,原因在前面解释过了。 注意,如果要通过location.*获得数据来构造新的字符串(特别是用于HTML和JavaScript)时,不要想当然地假定构造出来的字符串已经得到正确转义,那并不可靠。正浏览器会保留location.search属性(此属性就是URL查询字符串里的内容)里的尖括号不进行转义。而Chrome则相反,会对尖括号进行转义,但对双引号(n)和反斜杠则不做处理。而绝大多数浏览器对片段ID的部分都不做任何转义处理。👨⚕️👠💰😃👍 history对象:这个对象提供了几种不太常用的方法,能让每个窗口根据浏览记录前后切换移动,类似于点击浏览器界面上的“前进”和“后退”按钮。虽然没有办法精确地查看之前访问过的某URL;但可以通过一个估算的数字偏移量,如history.go(-2)进行定位。 screen对象:一个用于检查屏幕和浏览器窗口的大小、显示器DPI、色深等信息的基本API,它可以帮助优化网站在特定显示设备上的呈现效果。 👨🦱🩲📞😇🤳 navigator对象:用于查询浏览器版本、所在的操作系统、已安装的插件列表的接口。 document对象:到目前为止最复杂的层级对象,这是访问当前页面的文档对象模型(Document Object Model,DOM)的入口;我们会在下一节详细讨论这个模型。它有部分函数与文档结构其实并无关系,但也归类到这个对象层级里去了,这多半是当初设计时随意为之的结果。这种例子包括操作cookie的document.cookie,在当前页面中加入额外HTML代码的document.write(...),执行某些所见即所得(WYSIWYG)编辑任务时要用到的 document.execCommand(...) 注意:有意思的是,其实通过navigator和screen两个对象获得的信息就能非常可靠地识别很多独一无二的用户。Electronic Frontier Foundation公司做的Panopticlick项目演示( ) 就展示了这一广为人知的特点。👳👠🛏😅👃 另外还有几个编程语言必备的提供简单字符串处理或数学运算功能的对象。例如Math.randomO实现了一个不安全、可预期的伪随机数产生器(很不幸,大多数浏览器e现在还不支持更安全的PRNG方式),而String.fromCharCode()则可以把数字型的值转换成Unicode字符串。在常规Web应用里无法访问需要使用特权执行的环境里,还有相当数量与特定任务有关的对象。 注意:访问任何浏览器端对象时,要记住,尽管JavaScript并不使用以NUL空字符结尾的ASCIZ类型字符串,但因为JavaScript所依托的浏览器(往往用C或C++开发)有时候会用到这种字符串。因此,如果给DOM属性赋值时包含NUL或给JavaScript原生函数提供的字符串里包含NUL,就有可能产生难以预期和不统一的运行结果。譬如几乎所有浏览器在对location.*赋值时,都会在NUL字符处做截断,但D0M里*.innerHTML的同样情况却只有寥寥几种引擎才会这么处理。 👨🚒👜📥😔👀 #f185: 1.文档对象模型 文档对象模型(DOM)通过document对象访问文档的层级结构,这个层级结构其实就对应着HTML解析器里当前文档的树状结构化内存映像。HTML解析器产生的这个对象树上面,布满了整个页面里所有的HTML元素,还有这些元素的标签相关方法和属性以及相关联的CSS数据。浏览器就是通过这个解析后的映像而非HTML源文件本身,展现出当前文档的样子并对其内容进行更新的。🧑🚀🧣🪗😚🤝 类似于访问任何常规对象,JavaScript能直接访问DOM节点。例如,以下代码会指向文档的<body>区块里第五个标签,然后再找到内嵌的第一个子标签,并把这个元素的CSS颜色设置为红色: [mw_shl_code=javascript,true] document.body,children[4].children[0].style.color="red"; [/mw_shl_code]👨🎨🧣✏😔🖕 为避免在DOM树里东翻西找地获取某个嵌套得特别深的元素,浏览器提供了几种在文档里查找和定位函数的方式,如getElementById(...)和getElementsByTagName(...),还有一些略显多余的分组机制,如frames[]、images[]或forms[]。这些特性使得以下两行代码可以直接引用某个元素,而无论该元素处于文档层级的哪个位置: [mw_shl_code=javascript,true] document.getElementsByTagName("input")[2].value="Hi mom!"; document.images[7].src="/example.jpg";[/mw_shl_code] ✍🦼🥑📳🦜 由于历史原因,某些特定HTML元素(<img>、<form>、<embed>、<object>和<applet>)的名称也可以直接在document这个命名空间里使用,如下例代码所示: [mw_shl_code=javascript,true]<img name="hello" src="http://www.example.com/"> 👂🚐🥚☣🦮 <script> alert(document.hello.src); </script>[/mw_shl_code] 这种document条目的写法有可能对getElementByld或body这样的内置函数和对象产生干扰。例如,构建表单时,如果允许用户设置某个标签(tag)的名称,就可能引入不安全因素。🥷🩰💊🥱👄 除了提供对文档抽象映像的访问,许多DOM节点的innerHTML和outerHTML属性也能被访问到,这导致文档树的部分内容可以按合规序列化后的HTML字符串格式读取到。有趣的是,通过脚本还可以把这些属性设置为特定的HTML数据,达到重写DOM树任意位置内容的效果。后者的例子如下: [mw_shl_code=javascript,true] document.getElementById("output").innerHTML = "<b>Hi mom!</b>"; [/mw_shl_code]👓💳👄 只能使用合规的完整闭合的HTML区块对每个innerHTML节点进行赋值,因为这样才不会改变被重写段落之外的文档层级结构。如果格式不对,在重写发生之前输人的数据会先按照规定的语法进行强制转换。因此,以下例子的效果并非如预期的那样;也就是说,并不会显示粗体的“Hi mom!”,也不会令后面部分的文档变成斜体: [mw_shl_code=javascript,true] some_element.innerHTML = "<b>Hi"; some_element.innerHTML += "mom!</b><i>";[/mw_shl_code]👴🧢💰🤖👁 实际上,这两段赋值会分别进行处理和纠错,所产生的结果大抵相当于: [mw_shl_code=javascript,true] some_element.innerHTML = "<b>Hi</b> mom!<i></i>"; [/mw_shl_code]👵🧢⚒💩🤞 谨记,使用innerHTML机制一定要加倍小心。如果没有进行正确的HTML转义处理,不但innerHTML先天就容易导致代码注人,而且往往浏览器从DOM再还原到HTML序列化的算法实现也并不完美。一个WebKit例子描述如下: [mw_shl_code=javascript,true] <textarea> </textarea><script>alert(1)</script> 👎🚠🦀🈚🐮 </textarea>[/mw_shl_code] 因为<textarea>语义的含糊,这段看上去似乎很明确的标记代码,在解析成DOM树后如果再通过innerHTML进行读取时,就会错误地变成以下内容: [mw_shl_code=javascript,true] <textarea>🥷🩴🪝😭🖐 </textarea><script>alert(1)</script> </textarea> [/mw_shl_code] 这种情况下,即使只是执行了一个非操作性质的序列化赋值(例如 some_element.innerHTML += ""),也会导致难以预料的脚本注入。类似的问题也会影响其他的浏览器。例如,IE浏览器的开发人员就没意识到MSHTML解析引擎会把反引号(')当成引号字符,所以他们的innerHTML代码在处理这种情况时就会出现问题。MSHTML解析器的实现里,以下标记语言:🧓🦺⚒😔🖕 [mw_shl_code=javascript,true]<img src="test.jpg" alt="``onload=alert(l)"> 会被重新序列化成: 🧑🍳🕶🏮🥰🤟 <img src=test.jpg alt=``onload=alert(l)>[/mw_shl_code] 即使排除各个浏览器自身的缺陷,innerHTML的问题仍然甚为严重,当前HTML5草案的10.3章节只是简单地提到某些由脚本创建的DOM结构,并不需要进行HTML序列化,这种情况下也就不会强制浏览器做什么处理了。这完全是后果自负的意思啊!#j317: #f464: 💅🎠🥩🈳🦊 2.对其他文裆的访问 JavaScript代码还可以访问另一脚本运行环境文档根(root hierarchy)的层级对象句柄。例如,默认情况下每个上下文环境都可以方便地用parent、top、opener和frames[]方式引用另一个环境里的顶级对象。调用window.open(...)函数创建新的网页窗口时也会返回一个引用,而以下这个示例里的语法则可以定位到一个已命名的窗口,同样也会返回相应的引用: [mw_shl_code=javascript,true] var window_handle = window.open("","window_name");👩✈️🩰📟😄👎 [/mw_shl_code] 一旦程序获得指向另一个脚本上下文的句柄,它就可以与之交互。简单的交互例子如下: [mw_shl_code=javascript,true] top.location.path = "/new_path.html";🧑🍳🧣📷🤔🤛 或 frames[2].document.getElementById("output").innerHTML = "Hi mom!";[/mw_shl_code] 👑🪟😈🦴 如果缺乏有效的句柄,JavaScript就不能在无关的文档之间进行交互。特别是,没有办法定位在完全独立的浏览过程里的另一个未命名窗口,至少要由已访问过的页面(用window.name属性可以实现)设置过窗口名称,才有可能被访问到。 评分
帖子热度 9330 ℃
|
|