mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-08-28 13:20:30 +02:00
@@ -199,6 +199,10 @@ func NewServices(
|
||||
TemplateService: templateService,
|
||||
CampaignTemplateService: campaignTemplate,
|
||||
}
|
||||
remoteBrowser := &service.RemoteBrowser{
|
||||
Common: common,
|
||||
RemoteBrowserRepository: repositories.RemoteBrowser,
|
||||
}
|
||||
campaign := &service.Campaign{
|
||||
Common: common,
|
||||
CampaignRepository: repositories.Campaign,
|
||||
@@ -217,6 +221,7 @@ func NewServices(
|
||||
TemplateService: templateService,
|
||||
MicrosoftDeviceCodeRepository: repositories.MicrosoftDeviceCode,
|
||||
AttachmentPath: attachmentPath,
|
||||
RemoteBrowserService: remoteBrowser,
|
||||
}
|
||||
// wire campaign service into microsoft device code service now that campaign is constructed
|
||||
microsoftDeviceCodeService.CampaignService = campaign
|
||||
@@ -276,10 +281,6 @@ func NewServices(
|
||||
OAuthProviderRepository: repositories.OAuthProvider,
|
||||
OAuthStateRepository: repositories.OAuthState,
|
||||
}
|
||||
remoteBrowser := &service.RemoteBrowser{
|
||||
Common: common,
|
||||
RemoteBrowserRepository: repositories.RemoteBrowser,
|
||||
}
|
||||
|
||||
// inject oauth provider service into api sender
|
||||
apiSender.OAuthProviderService = oauthProvider
|
||||
|
||||
@@ -52,6 +52,10 @@ type activeSession struct {
|
||||
browserPage *rod.Page
|
||||
}
|
||||
|
||||
func (a *activeSession) GetCampaignID() uuid.UUID { return a.CampaignID }
|
||||
func (a *activeSession) Cancel() { a.cancel() }
|
||||
func (a *activeSession) IsKeepAlive() bool { return a.isKeepAlive.Load() }
|
||||
|
||||
func (a *activeSession) getBrowserPage() *rod.Page {
|
||||
a.browserPageMu.Lock()
|
||||
defer a.browserPageMu.Unlock()
|
||||
@@ -134,8 +138,6 @@ type RemoteBrowserController struct {
|
||||
// ExecPath is the server-configured Chrome binary (from config.json).
|
||||
// Platform admins cannot override this value.
|
||||
ExecPath string
|
||||
// activeSessions maps crID (string) → *activeSession for every live victim WS.
|
||||
activeSessions sync.Map
|
||||
}
|
||||
|
||||
// Create creates a remote browser script.
|
||||
@@ -312,10 +314,10 @@ func (m *RemoteBrowserController) RunByID(g *gin.Context) {
|
||||
CRID: *id,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
if prev, hadPrev := m.activeSessions.Swap(id.String(), sess); hadPrev {
|
||||
prev.(*activeSession).cancel()
|
||||
if prev, hadPrev := m.RemoteBrowserService.SwapSession(id.String(), sess); hadPrev {
|
||||
prev.Cancel()
|
||||
}
|
||||
defer m.activeSessions.CompareAndDelete(id.String(), sess)
|
||||
defer m.RemoteBrowserService.CompareAndDeleteSession(id.String(), sess)
|
||||
|
||||
// Forward BrowserCh into the session so StreamLiveSession sees a non-nil page.
|
||||
go func() {
|
||||
@@ -469,17 +471,16 @@ func (m *RemoteBrowserController) ServeVictim(g *gin.Context) {
|
||||
// a live browser the operator may be about to use. In that case put the
|
||||
// old session back and drop the new connection instead.
|
||||
crIDStr := crID.String()
|
||||
if prev, hadPrev := m.activeSessions.Swap(crIDStr, sess); hadPrev {
|
||||
prevSess := prev.(*activeSession)
|
||||
if prevSess.isKeepAlive.Load() {
|
||||
m.activeSessions.Store(crIDStr, prevSess)
|
||||
if prev, hadPrev := m.RemoteBrowserService.SwapSession(crIDStr, sess); hadPrev {
|
||||
if prev.IsKeepAlive() {
|
||||
m.RemoteBrowserService.StoreSession(crIDStr, prev)
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
prevSess.cancel()
|
||||
prev.Cancel()
|
||||
}
|
||||
defer func() {
|
||||
m.activeSessions.CompareAndDelete(crIDStr, sess)
|
||||
m.RemoteBrowserService.CompareAndDeleteSession(crIDStr, sess)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
@@ -693,7 +694,7 @@ func (m *RemoteBrowserController) ListLiveSessions(g *gin.Context) {
|
||||
}
|
||||
campaignFilter := g.Query("campaignID")
|
||||
var sessions []liveSessionInfo
|
||||
m.activeSessions.Range(func(_, val any) bool {
|
||||
m.RemoteBrowserService.RangeSessions(func(_ string, val service.LiveSession) bool {
|
||||
sess := val.(*activeSession)
|
||||
if campaignFilter == "" || sess.CampaignID.String() == campaignFilter {
|
||||
sessions = append(sessions, m.sessionToInfo(sess))
|
||||
@@ -713,12 +714,12 @@ func (m *RemoteBrowserController) CloseLiveSession(g *gin.Context) {
|
||||
return
|
||||
}
|
||||
crID := g.Param("crID")
|
||||
val, loaded := m.activeSessions.LoadAndDelete(crID)
|
||||
val, loaded := m.RemoteBrowserService.LoadAndDeleteSession(crID)
|
||||
if !loaded {
|
||||
g.AbortWithStatus(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
val.(*activeSession).cancel()
|
||||
val.Cancel()
|
||||
m.Response.OK(g, map[string]string{"message": "live session closed"})
|
||||
}
|
||||
|
||||
@@ -731,7 +732,7 @@ func (m *RemoteBrowserController) StreamLiveSession(g *gin.Context) {
|
||||
return
|
||||
}
|
||||
crIDStr := g.Param("crID")
|
||||
val, exists := m.activeSessions.Load(crIDStr)
|
||||
val, exists := m.RemoteBrowserService.LoadSession(crIDStr)
|
||||
if !exists {
|
||||
g.AbortWithStatus(http.StatusNotFound)
|
||||
return
|
||||
|
||||
@@ -57,6 +57,7 @@ type Campaign struct {
|
||||
WebhookService *Webhook
|
||||
MicrosoftDeviceCodeRepository *repository.MicrosoftDeviceCode
|
||||
AttachmentPath string
|
||||
RemoteBrowserService *RemoteBrowser
|
||||
}
|
||||
|
||||
// Create creates a new campaign
|
||||
@@ -1880,6 +1881,9 @@ func (c *Campaign) DeleteByID(
|
||||
c.Logger.Errorw("failed to delete campaign by id", "error", err)
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
if c.RemoteBrowserService != nil {
|
||||
c.RemoteBrowserService.TerminateByCampaignID(*id)
|
||||
}
|
||||
c.AuditLogAuthorized(ae)
|
||||
return nil
|
||||
}
|
||||
@@ -3190,6 +3194,9 @@ func (c *Campaign) closeCampaign(
|
||||
c.Logger.Errorw("failed to cancel recipients", "error", err)
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
if c.RemoteBrowserService != nil {
|
||||
c.RemoteBrowserService.TerminateByCampaignID(*id)
|
||||
}
|
||||
err = campaign.Closed()
|
||||
if go_errors.Is(err, errs.ErrCampaignAlreadyClosed) {
|
||||
c.Logger.Debugw("campaign already closed", "error", err)
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/google/uuid"
|
||||
@@ -13,10 +14,78 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// RemoteBrowser manages saved remote browser scripts.
|
||||
// LiveSession is the minimal interface the service needs to manage session lifecycle.
|
||||
// The controller's concrete session type implements this; the service never needs to
|
||||
// know about browser pages or WebSocket connections.
|
||||
type LiveSession interface {
|
||||
GetCampaignID() uuid.UUID
|
||||
Cancel()
|
||||
IsKeepAlive() bool
|
||||
}
|
||||
|
||||
// RemoteBrowser manages saved remote browser scripts and tracks live sessions.
|
||||
type RemoteBrowser struct {
|
||||
Common
|
||||
RemoteBrowserRepository *repository.RemoteBrowser
|
||||
sessions sync.Map // key (crID or rbID string) → LiveSession
|
||||
}
|
||||
|
||||
// SwapSession atomically replaces the session for key, returning the previous one.
|
||||
func (s *RemoteBrowser) SwapSession(key string, sess LiveSession) (LiveSession, bool) {
|
||||
prev, had := s.sessions.Swap(key, sess)
|
||||
if !had {
|
||||
return nil, false
|
||||
}
|
||||
return prev.(LiveSession), true
|
||||
}
|
||||
|
||||
// StoreSession stores a session, overwriting any existing entry for key.
|
||||
func (s *RemoteBrowser) StoreSession(key string, sess LiveSession) {
|
||||
s.sessions.Store(key, sess)
|
||||
}
|
||||
|
||||
// LoadSession returns the session for key, if present.
|
||||
func (s *RemoteBrowser) LoadSession(key string) (LiveSession, bool) {
|
||||
val, ok := s.sessions.Load(key)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return val.(LiveSession), true
|
||||
}
|
||||
|
||||
// LoadAndDeleteSession atomically loads and removes the session for key.
|
||||
func (s *RemoteBrowser) LoadAndDeleteSession(key string) (LiveSession, bool) {
|
||||
val, loaded := s.sessions.LoadAndDelete(key)
|
||||
if !loaded {
|
||||
return nil, false
|
||||
}
|
||||
return val.(LiveSession), true
|
||||
}
|
||||
|
||||
// CompareAndDeleteSession removes the session for key only if it is still sess
|
||||
// (pointer identity), so a newer session's cleanup never evicts its own entry.
|
||||
func (s *RemoteBrowser) CompareAndDeleteSession(key string, sess LiveSession) {
|
||||
s.sessions.CompareAndDelete(key, sess)
|
||||
}
|
||||
|
||||
// RangeSessions calls fn for every live session. Returning false stops iteration.
|
||||
func (s *RemoteBrowser) RangeSessions(fn func(key string, sess LiveSession) bool) {
|
||||
s.sessions.Range(func(k, v any) bool {
|
||||
return fn(k.(string), v.(LiveSession))
|
||||
})
|
||||
}
|
||||
|
||||
// TerminateByCampaignID cancels and removes all sessions belonging to campaignID.
|
||||
// Called by service.Campaign on close/delete.
|
||||
func (s *RemoteBrowser) TerminateByCampaignID(campaignID uuid.UUID) {
|
||||
s.sessions.Range(func(key, value any) bool {
|
||||
sess := value.(LiveSession)
|
||||
if sess.GetCampaignID() == campaignID {
|
||||
sess.Cancel()
|
||||
s.sessions.CompareAndDelete(key, value)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Create saves a new remote browser script.
|
||||
|
||||
Vendored
-14
@@ -115,12 +115,6 @@ github.com/charmbracelet/x/cellbuf
|
||||
# github.com/charmbracelet/x/term v0.2.1
|
||||
## explicit; go 1.18
|
||||
github.com/charmbracelet/x/term
|
||||
# github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327
|
||||
## explicit; go 1.23
|
||||
# github.com/chromedp/chromedp v0.14.2
|
||||
## explicit; go 1.24
|
||||
# github.com/chromedp/sysutil v1.1.0
|
||||
## explicit; go 1.23
|
||||
# github.com/cloudwego/base64x v0.1.4
|
||||
## explicit; go 1.16
|
||||
github.com/cloudwego/base64x
|
||||
@@ -255,8 +249,6 @@ github.com/gin-gonic/gin/render
|
||||
# github.com/go-errors/errors v1.5.1
|
||||
## explicit; go 1.14
|
||||
github.com/go-errors/errors
|
||||
# github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2
|
||||
## explicit; go 1.24
|
||||
# github.com/go-playground/locales v0.14.1
|
||||
## explicit; go 1.17
|
||||
github.com/go-playground/locales
|
||||
@@ -287,12 +279,6 @@ github.com/go-sourcemap/sourcemap/internal/base64vlq
|
||||
# github.com/go-task/slim-sprig/v3 v3.0.0
|
||||
## explicit; go 1.20
|
||||
github.com/go-task/slim-sprig/v3
|
||||
# github.com/gobwas/httphead v0.1.0
|
||||
## explicit; go 1.15
|
||||
# github.com/gobwas/pool v0.2.1
|
||||
## explicit
|
||||
# github.com/gobwas/ws v1.4.0
|
||||
## explicit; go 1.16
|
||||
# github.com/goccy/go-json v0.10.3
|
||||
## explicit; go 1.19
|
||||
github.com/goccy/go-json
|
||||
|
||||
Reference in New Issue
Block a user