简述
自上一篇博客介绍浏览器通信以来已经过去将近两个月了,兜兜转转挖了不少坑,也走了很多弯路。期间研究saml2.0和单点登录等技术都最后无疾而终。
只有xss框架这部分坚持了下来,这个框架还有很多事情需要完善,不过总算是有了一点小成果,很开心~~~
代码放到了这里对照着看效果更好
https://github.com/littleworldwar/pybeef
0x00web服务器设计
(1)tornado处理请求和Application类
要编写一个Tornado应用中最多的工作是定义类继承Tornado的RequestHandler类,主要用于将特定的url对应到不同的类。
通过在类中定义get和post函数处理对应的请求,self.write()用于将内容输出到返回流中。
self.render()将特定的文件内容渲染后作为输出,指定文件名即可。
self.get_argument()获取请求的参数
self.set_header()设置HTTP返回头
如果碰到复杂的服务器需求需要重写Application类,在__init__函数中定义handlers和setting。Handlers主要是将所有的url路径和对应的handler类对应起来,Setting中设置了template_path 设置模板路径主要用于self.render()函数查找文件路径。
(2)控制台登录设计
为了处理登录表单和配置文件中的用户名密码进行校验,先要定义一个LoginPageHandler类,处理登录的post请求取出用户名密码等参数,将参数与用configparser解析配置文件的数据进行对比,一致则通过认证。
同时还有cookie的设计为了在已经登录过的情况下直接使用get方法就能进入控制台页面。
(3)提供给受害浏览器执行的js文件
处理xss漏洞产生的get请求,将hook.js作为请求的响应。同时生成32位随机数,在hook浏览器中设置cookie,设置hook_id作为hook浏览器的唯一标识。
(4)控制台请求hook浏览器的数据
连接数据库查询浏览器的数据,使用pymongo模块连接本地数据库。
将信息组织成json结构,由于mongodb默认插入的_id值属于Object不能放到输入流中,我们删除了_id这一项。并将这些信息按照顺序标好。
由于控制台使用AJAX跨域请求,需要设置返回头Access-Control-Allow-Orign,将其设置为通配符* 也就是匹配一切地址。否则浏览器不能读取返回的数据。
(5)hook浏览器的轮询处理
Hook浏览器执行了hook.js后台post方法发送浏览器信息,同时要带上在cookie中设置的hook_id来标识浏览器。要让AJAX请求同时携带cookie,在后台设置返回头 Access-Control-Allow-Credentials为true。
将收集的信息存入数据库,如果hook_id存在不会重复插入数据。
0x01hook浏览器的信息收集设计
(1)收集浏览器的user-agent(用户代理)
浏览器的user-agent是浏览器的身份标识,简称UA。每次向服务器发出请求的时候都会在请求头中带上UA参数。例如 Mozilla/5.0 (X11; Linux x86_64;) Gecko/20100101 Firefox/45.0。包含了浏览器的版本,操作系统的类型,浏览器的渲染引擎等信息
(2)收集浏览器版本
即便在UA中可以获取到部分浏览器的版本,但是UA是可以人为修改的,判断浏览器版本不能全依赖UA。而且例如IE edge浏览器的UA是 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393。可以看出其中同样包含了Chrome和Safari浏览器的信息,依靠UA并不能准确判断浏览器版本。浏览器中一般扩展了自己独特的方法,例如window.chrome window.opera这种与浏览器相关性很强的特性 。利用这样的特性可以帮助我们缩小范围。跟踪浏览器每一个版本的更新特性,在判断的时候甚至可以脱离UA。在判断IE浏览器的时候完全没有借助UA,只是将各种特性合在一起判断浏览器版本。
(3)获取受害者的ip地址和内网地址
我们采用了webrtc技术,webrtc是一种用于方便创建在线聊天web应用的技术,被内置在Firefox和Chrome浏览器中。webRTC(Web Real-Time Communications)是一种让web应用和网站在浏览器之间交换数据的技术,建立了一系列的标准可以实现点对点的数据交换不需要用户安装插件或者其他第三方应用。要想让两个浏览器通信就要获取真实的ip地址,我们只用这一部分的技术。
1, 首先判断浏览器是否支持webrtc
!!window.mozRTCPeerConnection || !!window.webkitRTCPeerConnection
2,webrtc的接口介绍:
RTCPeerConnection
表示一个在本地电脑和外部节点的WebRTC连接,用于处理节点间的数据流
RTCIceCandidate
表示一个为了创建节点连接的服务器
rtc.onicecandidate = eventHandler;
指定一个函数当rtc中出现icecandidate事件时响应当本地的ICE客户端需要通过信号服务器给另一个节点发送信息时。需要ICE客户端和远程节点协商,浏览器不知道信号如何处理,简单使用这个方法将ice证书发送给远程节点
rtc.createOffer(successCallBack,errorCallBack[,options])
初始创建一个sdp offer 包含了 ICEagent已经收集到的candidate 用于信道内发送请求连接或者升级已存在链接的配置successCallback 将会传递一个RTCSessionDescription对象描述新建的offer
sdp (session describe procotol) 会话描述协议,是描述点对点连接标准,sdp包含编码,源地址,和视频语音的时间。Sdp信息就包含在RTCSessionDescription中,RTCSessionDescription.sdp取得sdp。
rtc.setLocalDescription(sessionDescription)方法 改变本地连接的描述信息正在运行的连接会改变
图4代表探测出的相关sdp信息,再利用正则表达式过滤出ip地址即可。
0x03控制台页面的设计
在控制台页面中主要是显示收集的信息,采用的树形结构使用了ztree框架显示受控的主机浏览器同时用其第一个ip地址来作为特征标识。为了页面的美观采用了bootstrap3这一款css框架,主要是通过对常见的html元素添加标签来改变页面的样式。Ztree框架同样用json数据来定义树形文档,面对的主要需求是点击树中的不同浏览器要动态改变页面的内容,显示与之对应的信息。
主要思路就是使用AJAX请求将节点部分的信息替换成收集的浏览器信息,因为得到的是json数据这里我们采用Jquery库中的$.getJSON(url,function(data))
前一个参数代表请求的地址,后一个参数是一个处理得到的数据的函数,其中data就存储着返回的数据。所以我们把所有关于ztree的操作都放在这个函数中。
定义树形结构json 定义zTreeNodes name 是要显示的名字,open 代表初始状态是打开的 children 代表下面的节点部分 定义在[]框中可以定义多个。
zTreeNodes =[
{"name":"online",open:true, children:[
// { "name":data.hook0.host, "url":"http://g.cn", "target":"_blank"},
]
}
];
使用得到的data数据向tree中添加节点,将hook+i 设为了treeId 是为了在onclick回调函数中继续操作data
for(var i=0;i<JSONLength(data);i++){
zTreeNodes[0].children.push({"name":data["hook"+String(i)].IP.split(" or ")[0],
"url":"","treeId":"hook"+String(i),
});
}
在ztree框架可以捕捉各种样式的浏览器事件,我们要响应单击这个事件并制定对应的回调函数处理单击事件。
callback:{
onClick: zTreeOnClick
},
接着定义zTreeOnClick函数,这个函数传递了三个变量我们只用到了treeNode
function zTreeOnClick(event, treeId, treeNode) {
TreeNode就是浏览器节点信息
要在这个函数中改变页面的信息,使用Jquery选择器动态改变页面信息例如,$("#ua").html(data[treeNode.treeId].ua);
选择id为ua的元素改变其中的html,treeNode后的treeId不是函数的参数而是在节点中定义的treeId 值为hook+数字。
0x04网络拓补图的动态绘制设计
这一块没有使用框架,只是利用html5的canvas标签大致画了一个拓补图,展示一下hook浏览器所处的网络环境。我们在外部定义了两个点(point_x,point_y)表示画笔所在的位置,例如画了一张图片以后将点的位置移动到接下来划线的开始位置,同理划线之后也一样。但是在画分支的时候我们提取了分支点的坐标,让每一个分支都从同一点出发。
Canvas画图只有等到页面渲染完会使用到指定的canvas标签,否则会出现找不到canvas标签的错误,所以我们将所有操作放在以下函数中 代表所有dom结构渲染完毕后需要进行的操作。$(document).ready(function(){
先要指定标签,并取得context(上下文)context指定画图的形式为2d
var canvas_tag =document.getElementById("net_map");
var ctx = canvas_tag.getContext("2d");
基本的canvas操作:
添加图片操作 :drawImage函数中pic是图片元素,start_pic_x,y是开始节点的坐标,后两个是图片的宽和高
var pic =new Image();
pic.src ="static/img/"+pic_name;
pic.onload =function(){
ctx.drawImage(pic,start_pic_x,start_pic_y,50,50);
}
添加文字操作:font设置文字大小和字体,textAlign设置文字对齐方式 fillText(字符串)
ctx.font ="12px sans-serif";
ctx.textAlign ="start";
ctx.fillText(text,start_pic_x,start_pic_y+62);
划线操作:moveTo代表移动光标,lineTo则是划线到目标点,stroke代表正式将线画在上面,如果缺失就没有线段。
ctx.beginPath();
ctx.moveTo(start_line_x,start_line_y);
ctx.lineTo(start_line_x+80,end_line_y);
ctx.stroke();