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

View File

@ -38,7 +38,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.14
go-version: 1.22
- if: matrix.os == 'ubuntu-latest'
run: sudo apt-get update && sudo apt-get install -y gcc-multilib
- if: matrix.arch == '386'
@ -47,7 +47,7 @@ jobs:
run: echo "RELEASE=gophish-${{ github.event.release.tag_name }}-${{ matrix.releaseos }}-64bit" >> $GITHUB_ENV
- 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
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Build ${{ matrix.goos }}/${{ matrix.arch }}
run: go build -o ${{ matrix.bin }}
env:
@ -55,7 +55,7 @@ jobs:
GOARCH: ${{ matrix.arch }}
CGO_ENABLED: 1
- name: Upload to artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: ${{ env.RELEASE }}
path: ${{ matrix.bin }}
@ -65,8 +65,8 @@ jobs:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v2
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
path: bin
- name: Package Releases
@ -96,7 +96,7 @@ jobs:
done
done
- name: Upload to artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: releases
path: releases/*.zip
@ -106,7 +106,7 @@ jobs:
runs-on: ubuntu-latest
needs: package
steps:
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v4
with:
name: 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)
}
}
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]+}/summary", as.CampaignSummary)
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/summary", as.GroupsSummary)
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,
RoleID: role.ID,
PasswordChangeRequired: ur.PasswordChangeRequired,
AccountLocked: ur.AccountLocked,
}
err = models.PutUser(&user)
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
}
// checkForNewEmails logs into an IMAP account and checks unread emails
// for the rid campaign identifier.
// checkForNewEmails logs into an IMAP account and checks unread emails for the
// rid campaign identifier.
func checkForNewEmails(im models.IMAP) {
im.Host = im.Host + ":" + strconv.Itoa(int(im.Port)) // Append port
mailServer := Mailbox{

View File

@ -74,12 +74,13 @@ type CampaignStats struct {
// Event contains the fields for an event
// that occurs during the campaign
type Event struct {
Id int64 `json:"-"`
CampaignId int64 `json:"campaign_id"`
Email string `json:"email"`
Time time.Time `json:"time"`
Message string `json:"message"`
Details string `json:"details"`
Id int64 `json:"id"`
CampaignId int64 `json:"campaign_id"`
Email string `json:"email"`
Time time.Time `json:"time"`
Message string `json:"message"`
Details string `json:"details"`
FalsePositive bool `json:"false_positive"`
}
// 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
}
// 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
func AddEvent(e *Event, campaignID int64) error {
e.CampaignId = campaignID
@ -609,7 +637,7 @@ func PostCampaign(c *Campaign, uid int64) error {
return tx.Commit().Error
}
//DeleteCampaign deletes the specified campaign
// DeleteCampaign deletes the specified campaign
func DeleteCampaign(id int64) error {
log.WithFields(logrus.Fields{
"campaign_id": id,

6
static/css/main.css vendored
View File

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

View File

@ -42,6 +42,12 @@ var statuses = {
icon: "fa-exclamation",
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
"Email Reported": {
color: "#45d6ef",
@ -178,7 +184,7 @@ function completeCampaign() {
return new Promise(function (resolve, reject) {
api.campaignId.complete(campaign.id)
.success(function (msg) {
resolve()
resolve()
})
.error(function (data) {
reject(data.responseJSON.message)
@ -236,6 +242,56 @@ function exportAsCSV(scope) {
$("#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) {
request = campaign.timeline[event_idx]
details = JSON.parse(request.details)
@ -400,7 +456,13 @@ function renderTimeline(data) {
}
if (event.message == "Submitted Data") {
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>'
}
if (details.payload) {

View File

@ -255,6 +255,12 @@ var api = {
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: {
get: function() {
return query("/webhooks/", "GET", {}, false)