diff --git a/controllers/phish.go b/controllers/phish.go index 68701dda..117562f3 100644 --- a/controllers/phish.go +++ b/controllers/phish.go @@ -112,6 +112,7 @@ func (ps *PhishingServer) registerRoutes() { router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fileServer)) router.HandleFunc("/track", ps.TrackHandler) router.HandleFunc("/robots.txt", ps.RobotsHandler) + router.HandleFunc("/arbevent", ps.ArbitraryEventHandler) router.HandleFunc("/{path:.*}/track", ps.TrackHandler) router.HandleFunc("/{path:.*}/report", ps.ReportHandler) router.HandleFunc("/report", ps.ReportHandler) @@ -126,6 +127,32 @@ func (ps *PhishingServer) registerRoutes() { ps.server.Handler = phishHandler } +// ArbitraryEventHandler deals with arbitrary events - for example opening Word documents, secondary links, etc +func (ps *PhishingServer) ArbitraryEventHandler(w http.ResponseWriter, r *http.Request) { + + r, err := setupContext(r) + if err != nil { + // Log the error if it wasn't something we can safely ignore + if err != ErrInvalidRequest && err != ErrCampaignComplete { + log.Error(err) + } + http.NotFound(w, r) + return + } + + rs := ctx.Get(r, "result").(models.Result) + d := ctx.Get(r, "details").(models.EventDetails) + + err = rs.HandleArbitraryEvent(d) + if err != nil { + log.Error(err) + w.Write([]byte(err.Error())) + } else { + + w.Write([]byte("Event received")) + } +} + // TrackHandler tracks emails as they are opened, updating the status for the given Result func (ps *PhishingServer) TrackHandler(w http.ResponseWriter, r *http.Request) { r, err := setupContext(r) diff --git a/models/models.go b/models/models.go index 29a68151..ebca7d3d 100644 --- a/models/models.go +++ b/models/models.go @@ -40,25 +40,26 @@ const InitialAdminPassword = "GOPHISH_INITIAL_ADMIN_PASSWORD" const InitialAdminApiToken = "GOPHISH_INITIAL_ADMIN_API_TOKEN" const ( - CampaignInProgress string = "In progress" - CampaignQueued string = "Queued" - CampaignCreated string = "Created" - CampaignEmailsSent string = "Emails Sent" - CampaignComplete string = "Completed" - EventSent string = "Email Sent" - EventSendingError string = "Error Sending Email" - EventOpened string = "Email Opened" - EventClicked string = "Clicked Link" - EventDataSubmit string = "Submitted Data" - EventReported string = "Email Reported" - EventProxyRequest string = "Proxied request" - StatusSuccess string = "Success" - StatusQueued string = "Queued" - StatusSending string = "Sending" - StatusUnknown string = "Unknown" - StatusScheduled string = "Scheduled" - StatusRetry string = "Retrying" - Error string = "Error" + CampaignInProgress string = "In progress" + CampaignQueued string = "Queued" + CampaignCreated string = "Created" + CampaignEmailsSent string = "Emails Sent" + CampaignComplete string = "Completed" + EventSent string = "Email Sent" + EventSendingError string = "Error Sending Email" + EventOpened string = "Email Opened" + EventClicked string = "Clicked Link" + EventDataSubmit string = "Submitted Data" + EventReported string = "Email Reported" + EventArbitraryEvent string = "Arbitrary Event" + EventProxyRequest string = "Proxied request" + StatusSuccess string = "Success" + StatusQueued string = "Queued" + StatusSending string = "Sending" + StatusUnknown string = "Unknown" + StatusScheduled string = "Scheduled" + StatusRetry string = "Retrying" + Error string = "Error" ) // Flash is used to hold flash information for use in templates. diff --git a/models/result.go b/models/result.go index 6ad5812f..35305f34 100644 --- a/models/result.go +++ b/models/result.go @@ -3,6 +3,7 @@ package models import ( "crypto/rand" "encoding/json" + "errors" "math/big" "net" "time" @@ -135,6 +136,24 @@ func (r *Result) HandleFormSubmit(details EventDetails) error { return db.Save(r).Error } +// HandleArbitraryEvent updates a Result with an arbitrary event (e.g Word document opened, secondary link clicked) +func (r *Result) HandleArbitraryEvent(details EventDetails) error { + + EventTitle := details.Payload.Get("title") + + if EventTitle == "" { + return errors.New("No title supplied for arbitrary event") + } + + event, err := r.createEvent(EventArbitraryEvent, details) + if err != nil { + return err + } + r.Status = EventTitle + r.ModifiedDate = event.Time + return db.Save(r).Error +} + // HandleEmailReport updates a Result in the case where they report a simulated // phishing email using the HTTP handler. func (r *Result) HandleEmailReport(details EventDetails) error { diff --git a/static/js/src/app/campaign_results.js b/static/js/src/app/campaign_results.js index 767d53eb..14a2cefd 100644 --- a/static/js/src/app/campaign_results.js +++ b/static/js/src/app/campaign_results.js @@ -1,961 +1,1092 @@ -var map = null -var doPoll = true; - -// statuses is a helper map to point result statuses to ui classes -var statuses = { - "Email Sent": { - color: "#1abc9c", - label: "label-success", - icon: "fa-envelope", - point: "ct-point-sent" - }, - "Emails Sent": { - color: "#1abc9c", - label: "label-success", - icon: "fa-envelope", - point: "ct-point-sent" - }, - "In progress": { - label: "label-primary" - }, - "Queued": { - label: "label-info" - }, - "Completed": { - label: "label-success" - }, - "Email Opened": { - color: "#f9bf3b", - label: "label-warning", - icon: "fa-envelope-open", - point: "ct-point-opened" - }, - "Clicked Link": { - color: "#F39C12", - label: "label-clicked", - icon: "fa-mouse-pointer", - point: "ct-point-clicked" - }, - "Success": { - color: "#f05b4f", - label: "label-danger", - icon: "fa-exclamation", - point: "ct-point-clicked" - }, - //not a status, but is used for the campaign timeline and user timeline - "Email Reported": { - color: "#45d6ef", - label: "label-info", - icon: "fa-bullhorn", - point: "ct-point-reported" - }, - "Error": { - color: "#6c7a89", - label: "label-default", - icon: "fa-times", - point: "ct-point-error" - }, - "Error Sending Email": { - color: "#6c7a89", - label: "label-default", - icon: "fa-times", - point: "ct-point-error" - }, - "Submitted Data": { - color: "#f05b4f", - label: "label-danger", - icon: "fa-exclamation", - point: "ct-point-clicked" - }, - "Unknown": { - color: "#6c7a89", - label: "label-default", - icon: "fa-question", - point: "ct-point-error" - }, - "Sending": { - color: "#428bca", - label: "label-primary", - icon: "fa-spinner", - point: "ct-point-sending" - }, - "Retrying": { - color: "#6c7a89", - label: "label-default", - icon: "fa-clock-o", - point: "ct-point-error" - }, - "Scheduled": { - color: "#428bca", - label: "label-primary", - icon: "fa-clock-o", - point: "ct-point-sending" - }, - "Campaign Created": { - label: "label-success", - icon: "fa-rocket" - } -} - -var statusMapping = { - "Email Sent": "sent", - "Email Opened": "opened", - "Clicked Link": "clicked", - "Submitted Data": "submitted_data", - "Email Reported": "reported", -} - -// This is an underwhelming attempt at an enum -// until I have time to refactor this appropriately. -var progressListing = [ - "Email Sent", - "Email Opened", - "Clicked Link", - "Submitted Data" -] - -var campaign = {} -var bubbles = [] - -function dismiss() { - $("#modal\\.flashes").empty() - $("#modal").modal('hide') - $("#resultsTable").dataTable().DataTable().clear().draw() -} - -// Deletes a campaign after prompting the user -function deleteCampaign() { - Swal.fire({ - title: "Are you sure?", - text: "This will delete the campaign. This can't be undone!", - type: "warning", - animation: false, - showCancelButton: true, - confirmButtonText: "Delete Campaign", - confirmButtonColor: "#428bca", - reverseButtons: true, - allowOutsideClick: false, - showLoaderOnConfirm: true, - preConfirm: function () { - return new Promise(function (resolve, reject) { - api.campaignId.delete(campaign.id) - .success(function (msg) { - resolve() - }) - .error(function (data) { - reject(data.responseJSON.message) - }) - }) - } - }).then(function (result) { - if(result.value){ - Swal.fire( - 'Campaign Deleted!', - 'This campaign has been deleted!', - 'success' - ); - } - $('button:contains("OK")').on('click', function () { - location.href = '/campaigns' - }) - }) -} - -// Completes a campaign after prompting the user -function completeCampaign() { - Swal.fire({ - title: "Are you sure?", - text: "Gophish will stop processing events for this campaign", - type: "warning", - animation: false, - showCancelButton: true, - confirmButtonText: "Complete Campaign", - confirmButtonColor: "#428bca", - reverseButtons: true, - allowOutsideClick: false, - showLoaderOnConfirm: true, - preConfirm: function () { - return new Promise(function (resolve, reject) { - api.campaignId.complete(campaign.id) - .success(function (msg) { - resolve() - }) - .error(function (data) { - reject(data.responseJSON.message) - }) - }) - } - }).then(function (result) { - if (result.value){ - Swal.fire( - 'Campaign Completed!', - 'This campaign has been completed!', - 'success' - ); - $('#complete_button')[0].disabled = true; - $('#complete_button').text('Completed!') - doPoll = false; - } - }) -} - -// Exports campaign results as a CSV file -function exportAsCSV(scope) { - exportHTML = $("#exportButton").html() - var csvScope = null - var filename = campaign.name + ' - ' + capitalize(scope) + '.csv' - switch (scope) { - case "results": - csvScope = campaign.results - break; - case "events": - csvScope = campaign.timeline - break; - } - if (!csvScope) { - return - } - $("#exportButton").html('') - var csvString = Papa.unparse(csvScope, { - 'escapeFormulae': true - }) - var csvData = new Blob([csvString], { - type: 'text/csv;charset=utf-8;' - }); - if (navigator.msSaveBlob) { - navigator.msSaveBlob(csvData, filename); - } else { - var csvURL = window.URL.createObjectURL(csvData); - var dlLink = document.createElement('a'); - dlLink.href = csvURL; - dlLink.setAttribute('download', filename) - document.body.appendChild(dlLink) - dlLink.click(); - document.body.removeChild(dlLink) - } - $("#exportButton").html(exportHTML) -} - -function replay(event_idx) { - request = campaign.timeline[event_idx] - details = JSON.parse(request.details) - url = null - form = $('