mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-02-13 00:22:49 +00:00
259 lines
7.2 KiB
Go
259 lines
7.2 KiB
Go
package service
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/phishingclub/phishingclub/database"
|
|
"github.com/phishingclub/phishingclub/model"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// ProxySession represents an active MITM proxy session
|
|
type ProxySession struct {
|
|
ID string
|
|
CampaignRecipientID *uuid.UUID
|
|
CampaignID *uuid.UUID
|
|
RecipientID *uuid.UUID
|
|
Campaign *model.Campaign
|
|
Domain *database.Domain
|
|
TargetDomain string
|
|
Config sync.Map // map[string]ProxyServiceDomainConfig
|
|
CreatedAt time.Time
|
|
RequiredCaptures sync.Map // map[string]bool
|
|
CapturedData sync.Map // map[string]map[string]string
|
|
NextPageType atomic.Value // string - accessed concurrently by multiple requests
|
|
IsComplete atomic.Bool // accessed concurrently when checking capture completion
|
|
CookieBundleSubmitted atomic.Bool // accessed concurrently to prevent duplicate submissions
|
|
|
|
// client user-agent stored for analytics and logging
|
|
UserAgent string
|
|
}
|
|
|
|
// ProxySessionManager manages proxy session lifecycle and storage
|
|
type ProxySessionManager struct {
|
|
Common
|
|
sessions sync.Map // map[sessionID]*ProxySession
|
|
campaignRecipientSessions sync.Map // map[campaignRecipientID]sessionID
|
|
urlMappings sync.Map // map[rewritten URL]original URL
|
|
queryParameterMappings sync.Map // map[rewritten path][]ProxyServiceURLRewriteQueryParam
|
|
}
|
|
|
|
// NewProxySessionManager creates a new proxy session manager
|
|
func NewProxySessionManager(logger *zap.SugaredLogger) *ProxySessionManager {
|
|
return &ProxySessionManager{
|
|
Common: Common{
|
|
Logger: logger,
|
|
},
|
|
}
|
|
}
|
|
|
|
// GetSession retrieves a session by ID
|
|
func (m *ProxySessionManager) GetSession(sessionID string) (*ProxySession, bool) {
|
|
val, ok := m.sessions.Load(sessionID)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
session, ok := val.(*ProxySession)
|
|
return session, ok
|
|
}
|
|
|
|
// StoreSession stores a session
|
|
func (m *ProxySessionManager) StoreSession(sessionID string, session *ProxySession) {
|
|
m.sessions.Store(sessionID, session)
|
|
}
|
|
|
|
// DeleteSession deletes a session and its associated campaign recipient mapping
|
|
func (m *ProxySessionManager) DeleteSession(sessionID string) {
|
|
if val, ok := m.sessions.Load(sessionID); ok {
|
|
if session, ok := val.(*ProxySession); ok {
|
|
if session.CampaignRecipientID != nil {
|
|
m.campaignRecipientSessions.Delete(session.CampaignRecipientID.String())
|
|
}
|
|
}
|
|
m.sessions.Delete(sessionID)
|
|
}
|
|
}
|
|
|
|
// GetSessionByCampaignRecipient retrieves a session ID by campaign recipient ID
|
|
func (m *ProxySessionManager) GetSessionByCampaignRecipient(campaignRecipientID string) (string, bool) {
|
|
val, ok := m.campaignRecipientSessions.Load(campaignRecipientID)
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
sessionID, ok := val.(string)
|
|
return sessionID, ok
|
|
}
|
|
|
|
// StoreCampaignRecipientSession stores the mapping between campaign recipient and session
|
|
func (m *ProxySessionManager) StoreCampaignRecipientSession(campaignRecipientID string, sessionID string) {
|
|
m.campaignRecipientSessions.Store(campaignRecipientID, sessionID)
|
|
}
|
|
|
|
// StoreURLMapping stores a URL mapping for rewrite tracking
|
|
func (m *ProxySessionManager) StoreURLMapping(rewrittenURL string, originalURL string) {
|
|
m.urlMappings.Store(rewrittenURL, originalURL)
|
|
}
|
|
|
|
// GetURLMapping retrieves the original URL for a rewritten URL
|
|
func (m *ProxySessionManager) GetURLMapping(rewrittenURL string) (string, bool) {
|
|
val, ok := m.urlMappings.Load(rewrittenURL)
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
originalURL, ok := val.(string)
|
|
return originalURL, ok
|
|
}
|
|
|
|
// StoreQueryParameterMappings stores query parameter mappings for reverse lookup
|
|
func (m *ProxySessionManager) StoreQueryParameterMappings(rewrittenPath string, queryRules []ProxyServiceURLRewriteQueryParam) {
|
|
m.queryParameterMappings.Store(rewrittenPath, queryRules)
|
|
}
|
|
|
|
// GetQueryParameterMappings retrieves query parameter mappings for a rewritten path
|
|
func (m *ProxySessionManager) GetQueryParameterMappings(rewrittenPath string) ([]ProxyServiceURLRewriteQueryParam, bool) {
|
|
val, ok := m.queryParameterMappings.Load(rewrittenPath)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
queryRules, ok := val.([]ProxyServiceURLRewriteQueryParam)
|
|
return queryRules, ok
|
|
}
|
|
|
|
// RangeSessions iterates over all sessions
|
|
func (m *ProxySessionManager) RangeSessions(fn func(sessionID string, session *ProxySession) bool) {
|
|
m.sessions.Range(func(key, value interface{}) bool {
|
|
sessionID, ok := key.(string)
|
|
if !ok {
|
|
return true
|
|
}
|
|
session, ok := value.(*ProxySession)
|
|
if !ok {
|
|
return true
|
|
}
|
|
return fn(sessionID, session)
|
|
})
|
|
}
|
|
|
|
// ClearSessionsForProxy clears all sessions associated with a proxy configuration
|
|
func (m *ProxySessionManager) ClearSessionsForProxy(proxyID string) {
|
|
if proxyID == "" {
|
|
return
|
|
}
|
|
|
|
clearedCount := 0
|
|
|
|
m.sessions.Range(func(key, value interface{}) bool {
|
|
sessionID, ok := key.(string)
|
|
if !ok {
|
|
return true
|
|
}
|
|
session, ok := value.(*ProxySession)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
// check if this session's domain belongs to the proxy
|
|
if session.Domain != nil && session.Domain.ProxyID != nil {
|
|
if session.Domain.ProxyID.String() == proxyID {
|
|
m.DeleteSession(sessionID)
|
|
clearedCount++
|
|
m.Logger.Debugw("cleared session for proxy",
|
|
"sessionID", sessionID,
|
|
"proxyID", proxyID,
|
|
"domain", session.Domain.Name,
|
|
)
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
if clearedCount > 0 {
|
|
m.Logger.Infow("cleared all sessions for proxy",
|
|
"count", clearedCount,
|
|
"proxyID", proxyID,
|
|
)
|
|
}
|
|
}
|
|
|
|
// ClearSessionsForDomains clears all sessions associated with specific phishing domains
|
|
func (m *ProxySessionManager) ClearSessionsForDomains(phishingDomains []string) {
|
|
if len(phishingDomains) == 0 {
|
|
return
|
|
}
|
|
|
|
// create a map for fast lookup
|
|
domainMap := make(map[string]bool)
|
|
for _, domain := range phishingDomains {
|
|
domainMap[domain] = true
|
|
}
|
|
|
|
clearedCount := 0
|
|
|
|
m.sessions.Range(func(key, value interface{}) bool {
|
|
sessionID, ok := key.(string)
|
|
if !ok {
|
|
return true
|
|
}
|
|
session, ok := value.(*ProxySession)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
// check if this session's domain matches any of the affected domains
|
|
if session.Domain != nil {
|
|
domainName := session.Domain.Name
|
|
if domainMap[domainName] {
|
|
m.DeleteSession(sessionID)
|
|
clearedCount++
|
|
m.Logger.Debugw("cleared session for affected domain",
|
|
"sessionID", sessionID,
|
|
"domain", domainName,
|
|
)
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
if clearedCount > 0 {
|
|
m.Logger.Infow("cleared sessions for affected domains",
|
|
"count", clearedCount,
|
|
"domains", phishingDomains,
|
|
)
|
|
}
|
|
}
|
|
|
|
// CleanupExpiredSessions removes sessions older than maxAge
|
|
func (m *ProxySessionManager) CleanupExpiredSessions(maxAge time.Duration) int {
|
|
now := time.Now()
|
|
cleanedCount := 0
|
|
|
|
m.sessions.Range(func(key, value interface{}) bool {
|
|
sessionID, ok := key.(string)
|
|
if !ok {
|
|
return true
|
|
}
|
|
session, ok := value.(*ProxySession)
|
|
if !ok {
|
|
m.sessions.Delete(sessionID)
|
|
cleanedCount++
|
|
return true
|
|
}
|
|
|
|
sessionAge := now.Sub(session.CreatedAt)
|
|
if sessionAge > maxAge {
|
|
m.DeleteSession(sessionID)
|
|
cleanedCount++
|
|
}
|
|
return true
|
|
})
|
|
|
|
if cleanedCount > 0 {
|
|
m.Logger.Debugw("cleaned up expired sessions", "count", cleanedCount)
|
|
}
|
|
|
|
return cleanedCount
|
|
}
|