gophish/controllers/phish_test.go

416 lines
13 KiB
Go

package controllers
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"testing"
"github.com/gophish/gophish/config"
"github.com/gophish/gophish/models"
)
func getFirstCampaign(t *testing.T) models.Campaign {
campaigns, err := models.GetCampaigns(1)
if err != nil {
t.Fatalf("error getting first campaign from database: %v", err)
}
return campaigns[0]
}
func getFirstEmailRequest(t *testing.T) models.EmailRequest {
campaign := getFirstCampaign(t)
req := models.EmailRequest{
TemplateId: campaign.TemplateId,
Template: campaign.Template,
PageId: campaign.PageId,
Page: campaign.Page,
URL: "http://localhost.localdomain",
UserId: 1,
BaseRecipient: campaign.Results[0].BaseRecipient,
SMTP: campaign.SMTP,
FromAddress: campaign.SMTP.FromAddress,
}
err := models.PostEmailRequest(&req)
if err != nil {
t.Fatalf("error creating email request: %v", err)
}
return req
}
func openEmail(t *testing.T, ctx *testContext, rid string) {
resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid))
if err != nil {
t.Fatalf("error requesting /track endpoint: %v", err)
}
defer resp.Body.Close()
got, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("error reading response body from /track endpoint: %v", err)
}
expected, err := ioutil.ReadFile("static/images/pixel.png")
if err != nil {
t.Fatalf("error reading local transparent pixel: %v", err)
}
if !bytes.Equal(got, expected) {
t.Fatalf("unexpected tracking pixel data received. expected %#v got %#v", expected, got)
}
}
func openEmail404(t *testing.T, ctx *testContext, rid string) {
resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid))
if err != nil {
t.Fatalf("error requesting /track endpoint: %v", err)
}
defer resp.Body.Close()
got := resp.StatusCode
expected := http.StatusNotFound
if got != expected {
t.Fatalf("invalid status code received for /track endpoint. expected %d got %d", expected, got)
}
}
func reportedEmail(t *testing.T, ctx *testContext, rid string) {
resp, err := http.Get(fmt.Sprintf("%s/report?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid))
if err != nil {
t.Fatalf("error requesting /report endpoint: %v", err)
}
got := resp.StatusCode
expected := http.StatusNoContent
if got != expected {
t.Fatalf("invalid status code received for /report endpoint. expected %d got %d", expected, got)
}
}
func reportEmail404(t *testing.T, ctx *testContext, rid string) {
resp, err := http.Get(fmt.Sprintf("%s/report?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid))
if err != nil {
t.Fatalf("error requesting /report endpoint: %v", err)
}
got := resp.StatusCode
expected := http.StatusNotFound
if got != expected {
t.Fatalf("invalid status code received for /report endpoint. expected %d got %d", expected, got)
}
}
func clickLink(t *testing.T, ctx *testContext, rid string, expectedHTML string) {
resp, err := http.Get(fmt.Sprintf("%s/?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid))
if err != nil {
t.Fatalf("error requesting / endpoint: %v", err)
}
defer resp.Body.Close()
got, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("error reading payload from / endpoint response: %v", err)
}
if !bytes.Equal(got, []byte(expectedHTML)) {
t.Fatalf("invalid response received from / endpoint. expected %s got %s", got, expectedHTML)
}
}
func clickLink404(t *testing.T, ctx *testContext, rid string) {
resp, err := http.Get(fmt.Sprintf("%s/?%s=%s", ctx.phishServer.URL, models.RecipientParameter, rid))
if err != nil {
t.Fatalf("error requesting / endpoint: %v", err)
}
defer resp.Body.Close()
got := resp.StatusCode
expected := http.StatusNotFound
if got != expected {
t.Fatalf("invalid status code received for / endpoint. expected %d got %d", expected, got)
}
}
func transparencyRequest(t *testing.T, ctx *testContext, r models.Result, rid, path string) {
resp, err := http.Get(fmt.Sprintf("%s%s?%s=%s", ctx.phishServer.URL, path, models.RecipientParameter, rid))
if err != nil {
t.Fatalf("error requesting %s endpoint: %v", path, err)
}
defer resp.Body.Close()
got := resp.StatusCode
expected := http.StatusOK
if got != expected {
t.Fatalf("invalid status code received for / endpoint. expected %d got %d", expected, got)
}
tr := &TransparencyResponse{}
err = json.NewDecoder(resp.Body).Decode(tr)
if err != nil {
t.Fatalf("error unmarshaling transparency request: %v", err)
}
expectedResponse := &TransparencyResponse{
ContactAddress: ctx.config.ContactAddress,
SendDate: r.SendDate,
Server: config.ServerName,
}
if !reflect.DeepEqual(tr, expectedResponse) {
t.Fatalf("unexpected transparency response received. expected %v got %v", expectedResponse, tr)
}
}
func TestOpenedPhishingEmail(t *testing.T) {
ctx := setupTest(t)
defer tearDown(t, ctx)
campaign := getFirstCampaign(t)
result := campaign.Results[0]
if result.Status != models.StatusSending {
t.Fatalf("unexpected result status received. expected %s got %s", models.StatusSending, result.Status)
}
openEmail(t, ctx, result.RId)
campaign = getFirstCampaign(t)
result = campaign.Results[0]
lastEvent := campaign.Events[len(campaign.Events)-1]
if result.Status != models.EventOpened {
t.Fatalf("unexpected result status received. expected %s got %s", models.EventOpened, result.Status)
}
if lastEvent.Message != models.EventOpened {
t.Fatalf("unexpected event status received. expected %s got %s", lastEvent.Message, models.EventOpened)
}
if result.ModifiedDate != lastEvent.Time {
t.Fatalf("unexpected result modified date received. expected %s got %s", lastEvent.Time, result.ModifiedDate)
}
}
func TestReportedPhishingEmail(t *testing.T) {
ctx := setupTest(t)
defer tearDown(t, ctx)
campaign := getFirstCampaign(t)
result := campaign.Results[0]
if result.Status != models.StatusSending {
t.Fatalf("unexpected result status received. expected %s got %s", models.StatusSending, result.Status)
}
reportedEmail(t, ctx, result.RId)
campaign = getFirstCampaign(t)
result = campaign.Results[0]
lastEvent := campaign.Events[len(campaign.Events)-1]
if result.Reported != true {
t.Fatalf("unexpected result report status received. expected %v got %v", true, result.Reported)
}
if lastEvent.Message != models.EventReported {
t.Fatalf("unexpected event status received. expected %s got %s", lastEvent.Message, models.EventReported)
}
if result.ModifiedDate != lastEvent.Time {
t.Fatalf("unexpected result modified date received. expected %s got %s", lastEvent.Time, result.ModifiedDate)
}
}
func TestClickedPhishingLinkAfterOpen(t *testing.T) {
ctx := setupTest(t)
defer tearDown(t, ctx)
campaign := getFirstCampaign(t)
result := campaign.Results[0]
if result.Status != models.StatusSending {
t.Fatalf("unexpected result status received. expected %s got %s", models.StatusSending, result.Status)
}
openEmail(t, ctx, result.RId)
clickLink(t, ctx, result.RId, campaign.Page.HTML)
campaign = getFirstCampaign(t)
result = campaign.Results[0]
lastEvent := campaign.Events[len(campaign.Events)-1]
if result.Status != models.EventClicked {
t.Fatalf("unexpected result status received. expected %s got %s", models.EventClicked, result.Status)
}
if lastEvent.Message != models.EventClicked {
t.Fatalf("unexpected event status received. expected %s got %s", lastEvent.Message, models.EventClicked)
}
if result.ModifiedDate != lastEvent.Time {
t.Fatalf("unexpected result modified date received. expected %s got %s", lastEvent.Time, result.ModifiedDate)
}
}
func TestNoRecipientID(t *testing.T) {
ctx := setupTest(t)
defer tearDown(t, ctx)
resp, err := http.Get(fmt.Sprintf("%s/track", ctx.phishServer.URL))
if err != nil {
t.Fatalf("error requesting /track endpoint: %v", err)
}
got := resp.StatusCode
expected := http.StatusNotFound
if got != expected {
t.Fatalf("invalid status code received for /track endpoint. expected %d got %d", expected, got)
}
resp, err = http.Get(ctx.phishServer.URL)
if err != nil {
t.Fatalf("error requesting /track endpoint: %v", err)
}
got = resp.StatusCode
if got != expected {
t.Fatalf("invalid status code received for / endpoint. expected %d got %d", expected, got)
}
}
func TestInvalidRecipientID(t *testing.T) {
ctx := setupTest(t)
defer tearDown(t, ctx)
rid := "XXXXXXXXXX"
openEmail404(t, ctx, rid)
clickLink404(t, ctx, rid)
reportEmail404(t, ctx, rid)
}
func TestCompletedCampaignClick(t *testing.T) {
ctx := setupTest(t)
defer tearDown(t, ctx)
campaign := getFirstCampaign(t)
result := campaign.Results[0]
if result.Status != models.StatusSending {
t.Fatalf("unexpected result status received. expected %s got %s", models.StatusSending, result.Status)
}
openEmail(t, ctx, result.RId)
campaign = getFirstCampaign(t)
result = campaign.Results[0]
if result.Status != models.EventOpened {
t.Fatalf("unexpected result status received. expected %s got %s", models.EventOpened, result.Status)
}
models.CompleteCampaign(campaign.Id, 1)
openEmail404(t, ctx, result.RId)
clickLink404(t, ctx, result.RId)
campaign = getFirstCampaign(t)
result = campaign.Results[0]
if result.Status != models.EventOpened {
t.Fatalf("unexpected result status received. expected %s got %s", models.EventOpened, result.Status)
}
}
func TestRobotsHandler(t *testing.T) {
ctx := setupTest(t)
defer tearDown(t, ctx)
resp, err := http.Get(fmt.Sprintf("%s/robots.txt", ctx.phishServer.URL))
if err != nil {
t.Fatalf("error requesting /robots.txt endpoint: %v", err)
}
defer resp.Body.Close()
got := resp.StatusCode
expectedStatus := http.StatusOK
if got != expectedStatus {
t.Fatalf("invalid status code received for /track endpoint. expected %d got %d", expectedStatus, got)
}
expected := []byte("User-agent: *\nDisallow: /\n")
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("error reading response body from /robots.txt endpoint: %v", err)
}
if !bytes.Equal(body, expected) {
t.Fatalf("invalid robots.txt response received. expected %s got %s", expected, body)
}
}
func TestInvalidPreviewID(t *testing.T) {
ctx := setupTest(t)
defer tearDown(t, ctx)
bogusRId := fmt.Sprintf("%sbogus", models.PreviewPrefix)
openEmail404(t, ctx, bogusRId)
clickLink404(t, ctx, bogusRId)
reportEmail404(t, ctx, bogusRId)
}
func TestPreviewTrack(t *testing.T) {
ctx := setupTest(t)
defer tearDown(t, ctx)
req := getFirstEmailRequest(t)
openEmail(t, ctx, req.RId)
}
func TestPreviewClick(t *testing.T) {
ctx := setupTest(t)
defer tearDown(t, ctx)
req := getFirstEmailRequest(t)
clickLink(t, ctx, req.RId, req.Page.HTML)
}
func TestInvalidTransparencyRequest(t *testing.T) {
ctx := setupTest(t)
defer tearDown(t, ctx)
bogusRId := fmt.Sprintf("bogus%s", TransparencySuffix)
openEmail404(t, ctx, bogusRId)
clickLink404(t, ctx, bogusRId)
reportEmail404(t, ctx, bogusRId)
}
func TestTransparencyRequest(t *testing.T) {
ctx := setupTest(t)
defer tearDown(t, ctx)
campaign := getFirstCampaign(t)
result := campaign.Results[0]
rid := fmt.Sprintf("%s%s", result.RId, TransparencySuffix)
transparencyRequest(t, ctx, result, rid, "/")
transparencyRequest(t, ctx, result, rid, "/track")
transparencyRequest(t, ctx, result, rid, "/report")
// And check with the URL encoded version of a +
rid = fmt.Sprintf("%s%s", result.RId, "%2b")
transparencyRequest(t, ctx, result, rid, "/")
transparencyRequest(t, ctx, result, rid, "/track")
transparencyRequest(t, ctx, result, rid, "/report")
}
func TestRedirectTemplating(t *testing.T) {
ctx := setupTest(t)
defer tearDown(t, ctx)
p := models.Page{
Name: "Redirect Page",
HTML: "<html>Test</html>",
UserId: 1,
RedirectURL: "http://example.com/{{.RId}}",
}
err := models.PostPage(&p)
if err != nil {
t.Fatalf("error posting new page: %v", err)
}
smtp, _ := models.GetSMTP(1, 1)
template, _ := models.GetTemplate(1, 1)
group, _ := models.GetGroup(1, 1)
campaign := models.Campaign{Name: "Redirect campaign"}
campaign.UserId = 1
campaign.Template = template
campaign.Page = p
campaign.SMTP = smtp
campaign.Groups = []models.Group{group}
err = models.PostCampaign(&campaign, campaign.UserId)
if err != nil {
t.Fatalf("error creating campaign: %v", err)
}
client := http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
result := campaign.Results[0]
resp, err := client.PostForm(fmt.Sprintf("%s/?%s=%s", ctx.phishServer.URL, models.RecipientParameter, result.RId), url.Values{"username": {"test"}, "password": {"test"}})
if err != nil {
t.Fatalf("error requesting / endpoint: %v", err)
}
defer resp.Body.Close()
got := resp.StatusCode
expectedStatus := http.StatusFound
if got != expectedStatus {
t.Fatalf("invalid status code received for /track endpoint. expected %d got %d", expectedStatus, got)
}
expectedURL := fmt.Sprintf("http://example.com/%s", result.RId)
gotURL, err := resp.Location()
if err != nil {
t.Fatalf("error getting Location header from response: %v", err)
}
if gotURL.String() != expectedURL {
t.Fatalf("invalid redirect received. expected %s got %s", expectedURL, gotURL)
}
}