Compare commits

...

4 Commits

Author SHA1 Message Date
Paul Werther f7991f7bf1
Merge a79ce72535 into 9561846979 2024-11-21 01:14:03 +00:00
Jordan Wright 9561846979
Update workflow actions and Go versions (#3245)
This PR:

* Updates the versions of various actions used by the CI and release workflows
* Updates the release workflow to use Go version 1.22
* Updates the test matrix to use Go versions 1.21, 1.22, and 1.23

It also updates the CI workflow to run when pull requests are created or changed. This will help give feedback when formatting or tests are broken during a PR.

As a good example of why this is useful, you'll see that I needed to run `gofmt` to get this to pass! We should have caught that earlier and now we'll catch it moving forward.
2024-09-22 23:24:43 -05:00
Caetan 908886f2cd
Enforce account locks when creating new users (#3173)
Properly enforce account locks when new users are created

---------

Co-authored-by: Caetan Tojeiro Carpente <caetan.tojeiro@tier8.com>
2024-09-22 22:53:08 -05:00
Paul Werther a79ce72535 implement mark submitted data as false positive, reference #1915 2021-05-19 13:31:56 +02:00
12 changed files with 156 additions and 23 deletions

View File

@ -1,5 +1,7 @@
name: CI name: CI
on: [push] on:
- pull_request
- push
jobs: jobs:
build: build:
@ -7,17 +9,17 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
goVer: [1.16, 1.17, 1.18] goVer: [1.21, 1.22, 1.23]
steps: steps:
- name: Set up Go ${{ matrix.goVer }} - name: Set up Go ${{ matrix.goVer }}
uses: actions/setup-go@v1 uses: actions/setup-go@v5
with: with:
go-version: ${{ matrix.goVer }} go-version: ${{ matrix.goVer }}
id: go id: go
- name: Check out code into the Go module directory - name: Check out code into the Go module directory
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Get dependencies - name: Get dependencies
run: | run: |
@ -31,4 +33,4 @@ jobs:
run: diff -u <(echo -n) <(gofmt -d .) run: diff -u <(echo -n) <(gofmt -d .)
- name: Test - name: Test
run: go test -v ./... run: go test ./...

View File

@ -38,7 +38,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: 1.14 go-version: 1.22
- if: matrix.os == 'ubuntu-latest' - if: matrix.os == 'ubuntu-latest'
run: sudo apt-get update && sudo apt-get install -y gcc-multilib run: sudo apt-get update && sudo apt-get install -y gcc-multilib
- if: matrix.arch == '386' - if: matrix.arch == '386'
@ -47,7 +47,7 @@ jobs:
run: echo "RELEASE=gophish-${{ github.event.release.tag_name }}-${{ matrix.releaseos }}-64bit" >> $GITHUB_ENV run: echo "RELEASE=gophish-${{ github.event.release.tag_name }}-${{ matrix.releaseos }}-64bit" >> $GITHUB_ENV
- if: matrix.os == 'windows-latest' - if: matrix.os == 'windows-latest'
run: echo "RELEASE=gophish-${{ github.event.release.tag_name }}-${{ matrix.releaseos }}-64bit" | Out-File -FilePath $env:GITHUB_ENV -Append # https://github.com/actions/runner/issues/1636 run: echo "RELEASE=gophish-${{ github.event.release.tag_name }}-${{ matrix.releaseos }}-64bit" | Out-File -FilePath $env:GITHUB_ENV -Append # https://github.com/actions/runner/issues/1636
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- name: Build ${{ matrix.goos }}/${{ matrix.arch }} - name: Build ${{ matrix.goos }}/${{ matrix.arch }}
run: go build -o ${{ matrix.bin }} run: go build -o ${{ matrix.bin }}
env: env:
@ -55,7 +55,7 @@ jobs:
GOARCH: ${{ matrix.arch }} GOARCH: ${{ matrix.arch }}
CGO_ENABLED: 1 CGO_ENABLED: 1
- name: Upload to artifacts - name: Upload to artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.RELEASE }} name: ${{ env.RELEASE }}
path: ${{ matrix.bin }} path: ${{ matrix.bin }}
@ -65,8 +65,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build needs: build
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions/download-artifact@v2 - uses: actions/download-artifact@v4
with: with:
path: bin path: bin
- name: Package Releases - name: Package Releases
@ -96,7 +96,7 @@ jobs:
done done
done done
- name: Upload to artifacts - name: Upload to artifacts
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: releases name: releases
path: releases/*.zip path: releases/*.zip
@ -106,7 +106,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: package needs: package
steps: steps:
- uses: actions/download-artifact@v2 - uses: actions/download-artifact@v4
with: with:
name: releases name: releases
path: releases/ path: releases/

View File

@ -135,3 +135,18 @@ func (as *Server) CampaignComplete(w http.ResponseWriter, r *http.Request) {
JSONResponse(w, models.Response{Success: true, Message: "Campaign completed successfully!"}, http.StatusOK) JSONResponse(w, models.Response{Success: true, Message: "Campaign completed successfully!"}, http.StatusOK)
} }
} }
func (as *Server) FalsePositive(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, _ := strconv.ParseInt(vars["id"], 0, 64)
rid, _ := vars["rid"]
switch {
case r.Method == "GET":
err := models.MarkEvent(id, rid)
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error marking event as false positive"}, http.StatusInternalServerError)
return
}
JSONResponse(w, models.Response{Success: true, Message: "Event marked as false positive!"}, http.StatusOK)
}
}

View File

@ -67,6 +67,7 @@ func (as *Server) registerRoutes() {
router.HandleFunc("/campaigns/{id:[0-9]+}/results", as.CampaignResults) router.HandleFunc("/campaigns/{id:[0-9]+}/results", as.CampaignResults)
router.HandleFunc("/campaigns/{id:[0-9]+}/summary", as.CampaignSummary) router.HandleFunc("/campaigns/{id:[0-9]+}/summary", as.CampaignSummary)
router.HandleFunc("/campaigns/{id:[0-9]+}/complete", as.CampaignComplete) router.HandleFunc("/campaigns/{id:[0-9]+}/complete", as.CampaignComplete)
router.HandleFunc("/falsepositive/{id:[0-9]+}/rid/{rid:[a-zA-Z0-9]+}", as.FalsePositive)
router.HandleFunc("/groups/", as.Groups) router.HandleFunc("/groups/", as.Groups)
router.HandleFunc("/groups/summary", as.GroupsSummary) router.HandleFunc("/groups/summary", as.GroupsSummary)
router.HandleFunc("/groups/{id:[0-9]+}", as.Group) router.HandleFunc("/groups/{id:[0-9]+}", as.Group)

View File

@ -109,6 +109,7 @@ func (as *Server) Users(w http.ResponseWriter, r *http.Request) {
Role: role, Role: role,
RoleID: role.ID, RoleID: role.ID,
PasswordChangeRequired: ur.PasswordChangeRequired, PasswordChangeRequired: ur.PasswordChangeRequired,
AccountLocked: ur.AccountLocked,
} }
err = models.PutUser(&user) err = models.PutUser(&user)
if err != nil { if err != nil {

View File

@ -0,0 +1,6 @@
-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
ALTER TABLE `events` ADD COLUMN false_positive BOOLEAN DEFAULT 0;
-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back

View File

@ -0,0 +1,6 @@
-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
ALTER TABLE events ADD COLUMN false_positive BOOLEAN DEFAULT 0;
-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back

View File

@ -115,8 +115,8 @@ func (im *Monitor) Shutdown() error {
return nil return nil
} }
// checkForNewEmails logs into an IMAP account and checks unread emails // checkForNewEmails logs into an IMAP account and checks unread emails for the
// for the rid campaign identifier. // rid campaign identifier.
func checkForNewEmails(im models.IMAP) { func checkForNewEmails(im models.IMAP) {
im.Host = im.Host + ":" + strconv.Itoa(int(im.Port)) // Append port im.Host = im.Host + ":" + strconv.Itoa(int(im.Port)) // Append port
mailServer := Mailbox{ mailServer := Mailbox{

View File

@ -74,12 +74,13 @@ type CampaignStats struct {
// Event contains the fields for an event // Event contains the fields for an event
// that occurs during the campaign // that occurs during the campaign
type Event struct { type Event struct {
Id int64 `json:"-"` Id int64 `json:"id"`
CampaignId int64 `json:"campaign_id"` CampaignId int64 `json:"campaign_id"`
Email string `json:"email"` Email string `json:"email"`
Time time.Time `json:"time"` Time time.Time `json:"time"`
Message string `json:"message"` Message string `json:"message"`
Details string `json:"details"` Details string `json:"details"`
FalsePositive bool `json:"false_positive"`
} }
// EventDetails is a struct that wraps common attributes we want to store // EventDetails is a struct that wraps common attributes we want to store
@ -154,6 +155,33 @@ func (c *Campaign) UpdateStatus(s string) error {
return db.Table("campaigns").Where("id=?", c.Id).Update("status", s).Error return db.Table("campaigns").Where("id=?", c.Id).Update("status", s).Error
} }
// Sets the False Positive-field for the specified eventid to true.
// Checks if all Events with the specific email in that campaign where data has been submitted are set to false_positive "true" and if thats the case changes
// the status in the "results" table for that recipient back to "Clicked Link" hence it won´t be count in the Submitted statistic until new data is submitted
func MarkEvent(eveId int64, rid string) error {
var mailcount int64
var fpcount int64
s := Event{}
query := db.Table("events").Where("id=?", eveId).Update("false_positive", true).Error
if query != nil {
log.Errorf("Problem editing false_positive in database: Table \"events\" on id %d", eveId)
}
mailquery := db.Table("events").Where("id = ?", eveId)
mailquery.Select("id, campaign_id, email, time, message, details, false_positive")
err := mailquery.Scan(&s).Error
if err != nil {
log.Error(err)
return err
}
//Counter to check if all Data Submitted is flaged as false/positive
db.Table("events").Select("Count(*)").Where("email = ? AND message = ?", s.Email, "Submitted Data").Count(&mailcount)
db.Table("events").Select("Count(*)").Where("email = ? AND message = ? AND false_positive = ?", s.Email, "Submitted Data", true).Count(&fpcount)
if mailcount == fpcount {
return db.Table("results").Where("campaign_id = ? AND r_id = ?", s.CampaignId, rid).Update("status", "Clicked Link").Error
}
return query
}
// AddEvent creates a new campaign event in the database // AddEvent creates a new campaign event in the database
func AddEvent(e *Event, campaignID int64) error { func AddEvent(e *Event, campaignID int64) error {
e.CampaignId = campaignID e.CampaignId = campaignID
@ -609,7 +637,7 @@ func PostCampaign(c *Campaign, uid int64) error {
return tx.Commit().Error return tx.Commit().Error
} }
//DeleteCampaign deletes the specified campaign // DeleteCampaign deletes the specified campaign
func DeleteCampaign(id int64) error { func DeleteCampaign(id int64) error {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"campaign_id": id, "campaign_id": id,

6
static/css/main.css vendored
View File

@ -741,4 +741,10 @@ table.dataTable {
} }
#password-strength-container { #password-strength-container {
height: 40px; height: 40px;
}
.ms-1 {
margin-left: 1em;
}
.mt-1 {
margin-top: 1em;
} }

View File

@ -42,6 +42,12 @@ var statuses = {
icon: "fa-exclamation", icon: "fa-exclamation",
point: "ct-point-clicked" point: "ct-point-clicked"
}, },
"True/False":{
color: "#6c7a89",
label: "label-info",
icon: "fa-eraser",
point: "ct-point-true_false"
},
//not a status, but is used for the campaign timeline and user timeline //not a status, but is used for the campaign timeline and user timeline
"Email Reported": { "Email Reported": {
color: "#45d6ef", color: "#45d6ef",
@ -178,7 +184,7 @@ function completeCampaign() {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
api.campaignId.complete(campaign.id) api.campaignId.complete(campaign.id)
.success(function (msg) { .success(function (msg) {
resolve() resolve()
}) })
.error(function (data) { .error(function (data) {
reject(data.responseJSON.message) reject(data.responseJSON.message)
@ -236,6 +242,56 @@ function exportAsCSV(scope) {
$("#exportButton").html(exportHTML) $("#exportButton").html(exportHTML)
} }
function false_positive(rid, eventid, cid, repo) {
if(repo === 'true'){
Swal.fire({
title: "Are you sure?",
text: "This will flag the submitted data as False Positive and substract it from the count. Please make sure that they are invalid!",
type: "question",
animation: false,
showCancelButton: true,
reverseButtons: true,
allowOutsideClick: false,
showLoaderOnConfirm: true
}).then(function (result) {
if (result.value){
api.eventId.falsepositive(eventid, rid).success((function() {
refresh();
} ));
}
})
}
else{ // will be called if Reported is not marked
Swal.fire({
title: "Are you sure?",
text: "This will flag the submitted data as False Positive and substract it from the count. Please make sure that they are invalid!",
type: "question",
animation: false,
showCancelButton: true,
input: 'checkbox',
inputValue: 1,
inputPlaceholder: 'Mark as reported too',
confirmButtonText: "Continue",
confirmButtonColor: "#428bca",
reverseButtons: true,
allowOutsideClick: false,
showLoaderOnConfirm: true
}).then(function (result) {
if (result.value && !result.reported){ // Reports the rid as Reported and the submitted data as false positive
report_mail(escapeHtml(rid), cid);
api.eventId.falsepositive(eventid, rid).success((function() {
refresh();
} ));
} else if (result.value === 0){ // Marks the Event with the submitted data as false positive
api.eventId.falsepositive(eventid, rid).success((function() {
refresh();
}));
}
})
}
}
function replay(event_idx) { function replay(event_idx) {
request = campaign.timeline[event_idx] request = campaign.timeline[event_idx]
details = JSON.parse(request.details) details = JSON.parse(request.details)
@ -400,7 +456,13 @@ function renderTimeline(data) {
} }
if (event.message == "Submitted Data") { if (event.message == "Submitted Data") {
results += '<div class="timeline-replay-button"><button onclick="replay(' + i + ')" class="btn btn-success">' results += '<div class="timeline-replay-button"><button onclick="replay(' + i + ')" class="btn btn-success">'
results += '<i class="fa fa-refresh"></i> Replay Credentials</button></div>' results += '<i class="fa fa-refresh"></i> Replay Credentials</button>'
if(campaign.timeline[i].false_positive === true){
results += '<div class="alert alert-warning mt-1" role="alert">Marked as False Positive!</div></div>'
}
else{
results += '<button onclick="false_positive(\'' + record.id + '\',\'' + campaign.timeline[i].id + '\',\'' + campaign.id + '\',\'' + record.reported + '\')" id="false-positive-button" class="btn btn-warning ms-1"><i class="fa fa-ban"></i> False Positive</button></div>'
}
results += '<div class="timeline-event-details"><i class="fa fa-caret-right"></i> View Details</div>' results += '<div class="timeline-event-details"><i class="fa fa-caret-right"></i> View Details</div>'
} }
if (details.payload) { if (details.payload) {

View File

@ -255,6 +255,12 @@ var api = {
return query("/users/" + id, "DELETE", {}, true) return query("/users/" + id, "DELETE", {}, true)
} }
}, },
eventId:{
//marks an event as false positiv in the database
falsepositive: function (eventid, rid) {
return query("/falsepositive/" + eventid + "/rid/" + rid, "GET", {}, true)
}
},
webhooks: { webhooks: {
get: function() { get: function() {
return query("/webhooks/", "GET", {}, false) return query("/webhooks/", "GET", {}, false)