Added transparency handler to return information JSON when a "+" is appended to a valid result ID (ref #1057)

pull/1148/merge
Jordan Wright 2018-06-09 20:58:05 -05:00
parent 64c5e54c64
commit 1efb71d1e9
4 changed files with 118 additions and 9 deletions

View File

@ -6,7 +6,9 @@ import (
"net" "net"
"net/http" "net/http"
"strings" "strings"
"time"
"github.com/gophish/gophish/config"
ctx "github.com/gophish/gophish/context" ctx "github.com/gophish/gophish/context"
log "github.com/gophish/gophish/logger" log "github.com/gophish/gophish/logger"
"github.com/gophish/gophish/models" "github.com/gophish/gophish/models"
@ -21,6 +23,21 @@ var ErrInvalidRequest = errors.New("Invalid request")
// has already been marked as complete. // has already been marked as complete.
var ErrCampaignComplete = errors.New("Event received on completed campaign") var ErrCampaignComplete = errors.New("Event received on completed campaign")
// TransparencyResponse is the JSON response provided when a third-party
// makes a request to the transparency handler.
type TransparencyResponse struct {
Server string `json:"server"`
ContactAddress string `json:"contact_address"`
SendDate time.Time `json:"send_date"`
}
// TransparencySuffix (when appended to a valid result ID), will cause Gophish
// to return a transparency response.
const TransparencySuffix = "+"
// ServerName is the server type that is returned in the transparency response.
const ServerName = "gophish"
// CreatePhishingRouter creates the router that handles phishing connections. // CreatePhishingRouter creates the router that handles phishing connections.
func CreatePhishingRouter() http.Handler { func CreatePhishingRouter() http.Handler {
router := mux.NewRouter() router := mux.NewRouter()
@ -52,7 +69,15 @@ func PhishTracker(w http.ResponseWriter, r *http.Request) {
return return
} }
rs := ctx.Get(r, "result").(models.Result) rs := ctx.Get(r, "result").(models.Result)
rid := ctx.Get(r, "rid").(string)
d := ctx.Get(r, "details").(models.EventDetails) d := ctx.Get(r, "details").(models.EventDetails)
// Check for a transparency request
if strings.HasSuffix(rid, TransparencySuffix) {
TransparencyHandler(w, r)
return
}
err = rs.HandleEmailOpened(d) err = rs.HandleEmailOpened(d)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
@ -77,8 +102,15 @@ func PhishReporter(w http.ResponseWriter, r *http.Request) {
return return
} }
rs := ctx.Get(r, "result").(models.Result) rs := ctx.Get(r, "result").(models.Result)
rid := ctx.Get(r, "rid").(string)
d := ctx.Get(r, "details").(models.EventDetails) d := ctx.Get(r, "details").(models.EventDetails)
// Check for a transparency request
if strings.HasSuffix(rid, TransparencySuffix) {
TransparencyHandler(w, r)
return
}
err = rs.HandleEmailReport(d) err = rs.HandleEmailReport(d)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
@ -117,8 +149,16 @@ func PhishHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
rs := ctx.Get(r, "result").(models.Result) rs := ctx.Get(r, "result").(models.Result)
rid := ctx.Get(r, "rid").(string)
c := ctx.Get(r, "campaign").(models.Campaign) c := ctx.Get(r, "campaign").(models.Campaign)
d := ctx.Get(r, "details").(models.EventDetails) d := ctx.Get(r, "details").(models.EventDetails)
// Check for a transparency request
if strings.HasSuffix(rid, TransparencySuffix) {
TransparencyHandler(w, r)
return
}
p, err := models.GetPage(c.PageId, c.UserId) p, err := models.GetPage(c.PageId, c.UserId)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
@ -172,6 +212,18 @@ func RobotsHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "User-agent: *\nDisallow: /") fmt.Fprintln(w, "User-agent: *\nDisallow: /")
} }
// TransparencyHandler returns a TransparencyResponse for the provided result
// and campaign.
func TransparencyHandler(w http.ResponseWriter, r *http.Request) {
rs := ctx.Get(r, "result").(models.Result)
tr := &TransparencyResponse{
Server: ServerName,
SendDate: rs.SendDate,
ContactAddress: config.Conf.ContactAddress,
}
JSONResponse(w, tr, http.StatusOK)
}
// setupContext handles some of the administrative work around receiving a new request, such as checking the result ID, the campaign, etc. // setupContext handles some of the administrative work around receiving a new request, such as checking the result ID, the campaign, etc.
func setupContext(r *http.Request) (error, *http.Request) { func setupContext(r *http.Request) (error, *http.Request) {
err := r.ParseForm() err := r.ParseForm()
@ -179,10 +231,24 @@ func setupContext(r *http.Request) (error, *http.Request) {
log.Error(err) log.Error(err)
return err, r return err, r
} }
id := r.Form.Get(models.RecipientParameter) rid := r.Form.Get(models.RecipientParameter)
if id == "" { if rid == "" {
return ErrInvalidRequest, r return ErrInvalidRequest, r
} }
// Since we want to support the common case of adding a "+" to indicate a
// transparency request, we need to take care to handle the case where the
// request ends with a space, since a "+" is technically reserved for use
// as a URL encoding of a space.
if strings.HasSuffix(rid, " ") {
// We'll trim off the space
rid = strings.TrimRight(rid, " ")
// Then we'll add the transparency suffix
rid = fmt.Sprintf("%s%s", rid, TransparencySuffix)
}
// Finally, if this is a transparency request, we'll need to verify that
// a valid rid has been provided, so we'll look up the result with a
// trimmed parameter.
id := strings.TrimSuffix(rid, TransparencySuffix)
// Check to see if this is a preview or a real result // Check to see if this is a preview or a real result
if strings.HasPrefix(id, models.PreviewPrefix) { if strings.HasPrefix(id, models.PreviewPrefix) {
rs, err := models.GetEmailRequestByResultId(id) rs, err := models.GetEmailRequestByResultId(id)
@ -226,6 +292,7 @@ func setupContext(r *http.Request) (error, *http.Request) {
d.Browser["address"] = ip d.Browser["address"] = ip
d.Browser["user-agent"] = r.Header.Get("User-Agent") d.Browser["user-agent"] = r.Header.Get("User-Agent")
r = ctx.Set(r, "rid", rid)
r = ctx.Set(r, "result", rs) r = ctx.Set(r, "result", rs)
r = ctx.Set(r, "campaign", c) r = ctx.Set(r, "campaign", c)
r = ctx.Set(r, "details", d) r = ctx.Set(r, "details", d)

View File

@ -2,11 +2,13 @@ package controllers
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
"github.com/gophish/gophish/config"
"github.com/gophish/gophish/models" "github.com/gophish/gophish/models"
) )
@ -50,6 +52,12 @@ func (s *ControllersSuite) reportedEmail(rid string) {
s.Equal(resp.StatusCode, http.StatusNoContent) s.Equal(resp.StatusCode, http.StatusNoContent)
} }
func (s *ControllersSuite) reportEmail404(rid string) {
resp, err := http.Get(fmt.Sprintf("%s/report?%s=%s", ps.URL, models.RecipientParameter, rid))
s.Nil(err)
s.Equal(resp.StatusCode, http.StatusNotFound)
}
func (s *ControllersSuite) openEmail404(rid string) { func (s *ControllersSuite) openEmail404(rid string) {
resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ps.URL, models.RecipientParameter, rid)) resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ps.URL, models.RecipientParameter, rid))
s.Nil(err) s.Nil(err)
@ -76,6 +84,19 @@ func (s *ControllersSuite) clickLink404(rid string) {
s.Equal(resp.StatusCode, http.StatusNotFound) s.Equal(resp.StatusCode, http.StatusNotFound)
} }
func (s *ControllersSuite) transparencyRequest(r models.Result, rid, path string) {
resp, err := http.Get(fmt.Sprintf("%s%s?%s=%s", ps.URL, path, models.RecipientParameter, rid))
s.Nil(err)
defer resp.Body.Close()
s.Equal(resp.StatusCode, http.StatusOK)
tr := &TransparencyResponse{}
err = json.NewDecoder(resp.Body).Decode(tr)
s.Nil(err)
s.Equal(tr.ContactAddress, config.Conf.ContactAddress)
s.Equal(tr.SendDate, r.SendDate)
s.Equal(tr.Server, ServerName)
}
func (s *ControllersSuite) TestOpenedPhishingEmail() { func (s *ControllersSuite) TestOpenedPhishingEmail() {
campaign := s.getFirstCampaign() campaign := s.getFirstCampaign()
result := campaign.Results[0] result := campaign.Results[0]
@ -134,13 +155,9 @@ func (s *ControllersSuite) TestNoRecipientID() {
func (s *ControllersSuite) TestInvalidRecipientID() { func (s *ControllersSuite) TestInvalidRecipientID() {
rid := "XXXXXXXXXX" rid := "XXXXXXXXXX"
resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ps.URL, models.RecipientParameter, rid)) s.openEmail404(rid)
s.Nil(err) s.clickLink404(rid)
s.Equal(resp.StatusCode, http.StatusNotFound) s.reportEmail404(rid)
resp, err = http.Get(fmt.Sprintf("%s/?%s=%s", ps.URL, models.RecipientParameter, rid))
s.Nil(err)
s.Equal(resp.StatusCode, http.StatusNotFound)
} }
func (s *ControllersSuite) TestCompletedCampaignClick() { func (s *ControllersSuite) TestCompletedCampaignClick() {
@ -177,6 +194,7 @@ func (s *ControllersSuite) TestInvalidPreviewID() {
bogusRId := fmt.Sprintf("%sbogus", models.PreviewPrefix) bogusRId := fmt.Sprintf("%sbogus", models.PreviewPrefix)
s.openEmail404(bogusRId) s.openEmail404(bogusRId)
s.clickLink404(bogusRId) s.clickLink404(bogusRId)
s.reportEmail404(bogusRId)
} }
func (s *ControllersSuite) TestPreviewTrack() { func (s *ControllersSuite) TestPreviewTrack() {
@ -188,3 +206,25 @@ func (s *ControllersSuite) TestPreviewClick() {
req := s.getFirstEmailRequest() req := s.getFirstEmailRequest()
s.clickLink(req.RId, req.Page.HTML) s.clickLink(req.RId, req.Page.HTML)
} }
func (s *ControllersSuite) TestInvalidTransparencyRequest() {
bogusRId := fmt.Sprintf("bogus%s", TransparencySuffix)
s.openEmail404(bogusRId)
s.clickLink404(bogusRId)
s.reportEmail404(bogusRId)
}
func (s *ControllersSuite) TestTransparencyRequest() {
campaign := s.getFirstCampaign()
result := campaign.Results[0]
rid := fmt.Sprintf("%s%s", result.RId, TransparencySuffix)
s.transparencyRequest(result, rid, "/")
s.transparencyRequest(result, rid, "/track")
s.transparencyRequest(result, rid, "/report")
// And check with the URL encoded version of a +
rid = fmt.Sprintf("%s%s", result.RId, "%2b")
s.transparencyRequest(result, rid, "/")
s.transparencyRequest(result, rid, "/track")
s.transparencyRequest(result, rid, "/report")
}

View File

@ -155,6 +155,7 @@ func (s *ModelsSuite) TestMailLogSuccess(ch *check.C) {
Time: gotEvent.Time, Time: gotEvent.Time,
} }
ch.Assert(gotEvent, check.DeepEquals, expectedEvent) ch.Assert(gotEvent, check.DeepEquals, expectedEvent)
ch.Assert(result.SendDate, check.Equals, gotEvent.Time)
ms, err := GetMailLogsByCampaign(campaign.Id) ms, err := GetMailLogsByCampaign(campaign.Id)
ch.Assert(err, check.Equals, nil) ch.Assert(err, check.Equals, nil)

View File

@ -62,6 +62,7 @@ func (r *Result) HandleEmailSent() error {
if err != nil { if err != nil {
return err return err
} }
r.SendDate = event.Time
r.Status = EVENT_SENT r.Status = EVENT_SENT
r.ModifiedDate = event.Time r.ModifiedDate = event.Time
return db.Save(r).Error return db.Save(r).Error