mirror of https://github.com/gophish/gophish
Added transparency handler to return information JSON when a "+" is appended to a valid result ID (ref #1057)
parent
64c5e54c64
commit
1efb71d1e9
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue