admin管理员组

文章数量:1530017

引言

端口扫描、内存溢出、安全漏洞……这些大概是很多人提到黑客时会想到的。他们用的语言自然也是汇编、C这些贴近底层的高级语言。JavaScript似乎与这一切毫不相关,自诞生起相当长一段时间内,这门在浏览器内执行一些简单的校验和互动的脚本语言,是职业程序员看不上眼的玩具。然而,本文就是要谈谈Hacking with JavaScript——以JavaScript来“黑客”。

这之所以能成为一个题目,有两方面的原因。

一:今非昔比,随着web技术大行其道,JavaScript作为web应用前端的实际标准,也成为最热门的编程语言之一。在此过程中,不仅JavaScript原来的优点被人们认识到,它自身也在不断演化以适应更复杂和可靠的编程需求。

二:也是更重要的是对Hacking的理解。正如GNU项目发起人、自由软件基金会创始人RichardStallman所说,“Hacking——‘黑客行为’范围很广,从编写软件、恶作剧到探索MIT校园的地道,在MIT的大穹顶上放个好玩的东西……”可以看出这些行为有几个共同点,那就是创造性、有趣、经常带有冒险性(不一定是身体上的冒险)、行为本人往往不是为了实用,而是为了玩、显示自己的聪明或者探索未知【注1】,还有一点就是与数学家和理论物理学家这样的理论工作不同,都与实际世界有更紧密的联系。而媒体上对黑客的理解只是因为他们和所做的事的一小部分,即对计算机系统安全的破坏。

对本文题目的正当性做了辩护之后,很多行为都能被纳入Hacking with JavaScript的范畴,本文不准备讨论那些绕过JavaScript缺陷和安全上限制的tweaking,而是从运行环境的角度,简单介绍几种不同于通常网页的浏览器中运行JavaScript 的“后门”,看看这些“不走寻常路”的脚本【注2】。

地址栏

在web编程领域摸爬滚打时间久的老同志一定还记得,回到IE一统江湖的时代,在浏览器里调试JavaScript的工具十分有限,除了使用起来不够方便、应用也不广泛的微软的独立的脚本调试器,程序员往往就是在浏览器里裸战,或者用alert方法弹出信息,或者就是利用地址栏。将以Javascript:开头的一行代码输入地址栏,回车,浏览器就会执行该代码,而且能访问到当前页面的环境,例如文档对象和页面中脚本的各种变量、函数【注3】。比如javascript:alert(status)就会弹出在当前页面的脚本中定义的一个status的全局变量。这样做有时候比在代码里插入调试代码更直接方便,不过也仅仅用于临时性地排除一些错误。

比起用于调试,更重要的是事情本身——地址栏里不仅可以输入地址,浏览器会执行其中的JavaScript,这就相当于一个原始的JavaScript控制台。Bookmarklet正是利用了这一点。

所谓Bookmarklet,顾名思义,就像applet、servlet一样,指的是“书签小程序”。也就是在通常保存地址的书签里写入JavaScript,以完成某个功能【注4】。Google的给其网络书签添加新页面的“书签小程序”就是一个很好的例子,将该链接拖动到书签栏,以后浏览某网页时,只要点击该书签,就会弹出一个页面,页面上的表单包含了添加当前页面做书签的各项信息,地址和标题都已经预填了,用户还可以增加标签等信息,提交后这个页面就被添加到用户的Google网络书签里。

我们可以做一些有趣的书签小程序。现在很多人访问诸如新闻、购物网站的目录和列表页时都习惯在浏览器的新标签页里打开链接,但是有些网站,特别是很多国外的大网站(如经济学人、读卖新闻、Amazon等)仍然沿用在当前页面打开链接的传统,虽然我们可以按着Ctrl键或用链接上快捷菜单里的命令在新标签页里打开,但每次这样做稍显麻烦。当然我们也可以修改浏览器的标签页属性甚至像在Firefox里安装一些插件来强制所有链接都在新标签页里打开,但那样未免矫枉过正,在某些场合仍然是在当前页面打开链接方便。最可取的方法自然是需要时指定当前页面的链接都在新标签页里打开,书签小程序正适合完成这个工作。

新建一个书签,命名为“在新标签页里打开链接”,然后将如下代码粘贴到地址里:

javascript:(function(){function%20main(cw){for(var%20i=0;i<cw.frames.length;i++){main(cw.frames[i]);}modifyLink(cw.document);}function%20modifyLink(doc){var%20links=doc.getElementsByTagName('a');for(var%20i=0;i<links.length;i++){links[i].target='_blank';}}main(window);})()

保存后,每次需要时,先点一下这个书签即可。

因为书签小程序里的代码是保存在地址栏里,为了紧凑,所有不必要的空白字符都省去了,必要的空格则按照URL在规范转码为%20(这一点添加书签时,有些浏览器如Firefox会自动进行)。

压缩前的代码如下,本身十分简单,无需说明。

(function(){
	function main(cw){
		for (var i=0; i<cw.frames.length; i++){
			main(cw.frames[i]);
		}
		modifyLink(cw.document);
	}
	function modifyLink(doc){
		var links=doc.getElementsByTagName('a');
		for (var i=0;i<links.length;i++){
			links[i].target='_blank';
		}
	}
	main(window);
})()

再比如我若干年前经常在天涯论坛上看到有人刷屏挖坑,盼着分页。错过了首页的沙发板凳地板没关系,还有第二次第三次机会。可惜天涯不是按照贴子屏幕上的高度来分页的,所以再大的坑除了让大家多滚动几次屏幕外没有什么建设性作用。实际上这看似神秘的分页是由每页的楼层也就是回覆贴数量决定的,每页的楼层数量都很接近100【注5】,于是我写了一个“天涯分页”的书签小程序,在天涯帖子上使用时,如果当前页面的楼层接近一百了,就提示快要分页了,否则提示距离分页大约还需多少次回覆。事隔几年,我再去天涯论坛看时,发现盼着分页的风气好像不在,而且天涯的页面HTML也已经修改,原来的小程序失效了。

控制台

随着web系统前端发挥的作用越来越大,JavaScript脚本也变得越来越复杂,功能更强大的编写和调试工具应需而生。Firefox的Firebug插件就属于第一批登场的优秀的web开发工具。其中的控制台为JavaScript提供了这一类解释型语言常用的控制台,即测试和开发环境——输入脚本,运行,打印出运行结果,并且能访问当前页面的环境。这是比上一节说的地址栏更方便全面的hacking的场所。随后的各大浏览器都推出了集成的web开发工具,功能也都大同小异,JavaScript控制台是共同的功能项,在这里可以像网页原先的前端开发者一样,任意获取页面信息、转换和修改。此外,由于很多网页都使用了某种JavaScript库或框架,在控制台里也可以直接调用这些库的方法。如果说此处对页面的修改是临时性的、一次性的为了乐趣的涂鸦,获取和整理页面中的信息却可以有真实的用处。

我在CSDN的博客写了一系列关于Lotus Domino开发的文章后,申请了一个专栏,要将博客里的文章一篇篇手动添加到专栏里,也是个不大不小的重复的劳动:

1.      将鼠标移动到一篇文章的链接上。

2.      点击右键,弹开快捷菜单。

3.      选择菜单里的复制链接地址。

4.      转到管理专栏的页面。

5.      选中添加文章地址的文本框。

6.      粘贴刚刚复制的地址。

7.      用键盘或者鼠标将焦点移动到文本框右边的“添加”按钮。

8.      点击按钮完成一次添加。

9.      转回博客文章列表的页面。

以上九步原子化的步骤,需要重复好几十次,而且复制链接地址的时候还要小心别弄错。对于程序员来说,重复几乎意味着犯罪。于是我查看了页面的源代码,打开博客的目录视图页面,在浏览器的JavaScript控制台里运行如下脚本:

//自动添加文章到专栏,只选择标题格式类似"26. XXXX"的文章。
//第一部分,获取文章的地址。
var l=$('.link_title a'); //CSDN博客站点用了jQuery,所以在这里可以很方便地调用。
var urls=[];
for (i=0; i<l.length; i++){
    if (l.get(i).text.contains('.')){
        urls.push(l.get(i).href);
    }
} 
//第二部分,将文章添加到专栏。
var num=urls.length;
//使用管理专栏页面的id变量。也可以选择在管理专栏的页面运行这段脚本,那么就能直接引用页面里的id变量,不过要先将上面获得的地址打印出来,作为字符串使用。
var id="2013-10-23 12:09:39 020";
//下面修改并利用了原来的addArticle函数。
function addArticle(index) {
    if (index===num) return;
    var url = urls[index];
    var reg = /blog\.csdn\\/([\w]+)\/article\/details\/(\d+)/i;
    var mat = reg.exec(url);
    if (!mat) { alert('请输入正确的CSDN博文地址。'); return; }
    $.get("AddArticle.html", { id: id, artid: mat[2] }, function (ret) {
        if (ret.result == 1) {
            $('#art_ul').html(getLi(ret.id, ret.url, ret.title) + $('#art_ul').html());
            $('#b_num').html(parseInt($('#b_num').html()) + 1);
            index++;
            addArticle(index);
        } else {
            alert(ret.content);
        }
    });
}
addArticle(0);

运行……所有文章自动添加成功!

Greasemonkey

和互联网最初阶段的静态网页相比,动态网页的出现部分就是为了个性化,也就是对不同的用户呈现不同的内容。这种个性化不仅体现在功能上,还越来越多地体现在视觉外观上,各种博客、个人空间、社交网站都是典型。但所有这些个性化都有一个共同点,即都是网站的开发者预先设计的。如果网站的用户对操作和界面有不满,他们会抱怨,还可能会想要是怎样怎样就好了;如果这些用户本身也是设计者和开发者,他们就可能把自己的改进想法付诸实践。有人想到了这一点,更有人做出来,这就是Greasemonkey——一个Firefox的插件,能在网页打开时加载和执行用户创建的JavaScript脚本(称为用户脚本)。上一节提到在控制台里对网页的修改是临时的、一次性的,Greasemonkey则将这类修改固定下来并可以分享给别人,从而带给网站用户创造的巨大可能性,“油猴”的诞生和不少优秀的用户脚本都是不折不扣的精彩hacking。Greasemonkey风靡一时,出现了专门的聚合网站userscripts供人上传、下载和讨论用户脚本,不少其它浏览器也开始支持用户脚本。

和普通的网页上的JavaScript代码相比,用户脚本有几个特点:

1.      在浏览器的DOMContentLoaded事件发生时运行。DOMContentLoaded在一个网页的DOM载入后触发,而传统的window.onload事件则要等到页面上所有资源,包括图片和脚本都载入完成才触发,因而可能被延迟到很晚。

2.      脚本在Greasemonkey提供的沙盒模型的运行,以保证安全性,如不会直接读取和篡改原页面脚本中的变量和函数,但在必要的情况下仍然可以越过这个限制。

3.      脚本开头支持很多指令,包括@require可以引用其他JavaScript文件,例如CDN上的各种脚本库。

除了一般的Javascript和网页知识外,为Greasemonkey编写用户脚本需要学习的内容不多,在wiki.greasespot网站上都有介绍。userscripts上则有大量针对某个网站或者一般性的浏览的网友们的作品。像“热键翻页”(为大量列表网页提供左右键翻页的功能,http://userscripts/scripts/show/87895)、Clear Radio Button(清除选中的单选框,http://userscripts/scripts/show/6540)都是很方便的工具。

用户脚本虽然可以实现页面的自定义,但所有改动的基础仍然是原来的网页,因此脚本的开发很大程度上受到它的影响。如果原网页结构和代码混乱,编写用户脚本也会多费神;反之,一个清晰的网页则是良好的基础。此外原网页的修改往往会导致用户脚本失效,因此为了保持可用,用户脚本的作者必须跟上该网站的更新速度。对一些频繁修改的网站,这对相关脚本的作者意味着持续的投入。最后,网站的修改还可能使脚本的功能彻底失去可能,或者加入脚本的功能使其失去必要。例如,几年前我曾为了天涯论坛写了一个“天涯精灵”的脚本,功能包括高亮或单独显示某位用户的帖子和快速回帖等很多论坛都具备的特性,后来因为天涯的改版,这些功能或者失效,或者网站本身增添了。

 

注1:实际上这些特点都是紧密联系在一起的,单纯为了实用是产生不了最具创造性的成果的,普林斯顿高级研究所创始人之一的Abraham Flexner的名言“无用的知识的有用性”(THE USEFULNESS OF USELESS KNOWLEDGE)就是有趣的注脚。

                                                        

注2:对服务器端运行的JavaScript的探讨不在本文范围以内,如最初的Netscape公司的LiveWire和现在引人关注的Node.js。

 

注3:曾经的IE 6可以。在我最新做的测试中,IE 11仍然可以,而Firefox 26则无法访问当前页面的环境变量,但这仅限于在地址栏里直接输入脚本,如果采用下面所说的“书签小程序”,仍然可以访问。

 

注4:人们最初想到这种hacking时,在IE 6里保存这样一个bookmarklet,还会被提示“javascript没有已注册的协议……”。

 

注5:我检查过几页为102,101,102,102,99,100,99。

本文标签: 之内浏览器网页hackingJavaScript