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/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gophish/gophish/config"
|
||||
ctx "github.com/gophish/gophish/context"
|
||||
log "github.com/gophish/gophish/logger"
|
||||
"github.com/gophish/gophish/models"
|
||||
|
@ -21,6 +23,21 @@ var ErrInvalidRequest = errors.New("Invalid request")
|
|||
// has already been marked as complete.
|
||||
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.
|
||||
func CreatePhishingRouter() http.Handler {
|
||||
router := mux.NewRouter()
|
||||
|
@ -52,7 +69,15 @@ func PhishTracker(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
rs := ctx.Get(r, "result").(models.Result)
|
||||
rid := ctx.Get(r, "rid").(string)
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
|
@ -77,8 +102,15 @@ func PhishReporter(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
rs := ctx.Get(r, "result").(models.Result)
|
||||
rid := ctx.Get(r, "rid").(string)
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
|
@ -117,8 +149,16 @@ func PhishHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
rs := ctx.Get(r, "result").(models.Result)
|
||||
rid := ctx.Get(r, "rid").(string)
|
||||
c := ctx.Get(r, "campaign").(models.Campaign)
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
|
@ -172,6 +212,18 @@ func RobotsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
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.
|
||||
func setupContext(r *http.Request) (error, *http.Request) {
|
||||
err := r.ParseForm()
|
||||
|
@ -179,10 +231,24 @@ func setupContext(r *http.Request) (error, *http.Request) {
|
|||
log.Error(err)
|
||||
return err, r
|
||||
}
|
||||
id := r.Form.Get(models.RecipientParameter)
|
||||
if id == "" {
|
||||
rid := r.Form.Get(models.RecipientParameter)
|
||||
if rid == "" {
|
||||
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
|
||||
if strings.HasPrefix(id, models.PreviewPrefix) {
|
||||
rs, err := models.GetEmailRequestByResultId(id)
|
||||
|
@ -226,6 +292,7 @@ func setupContext(r *http.Request) (error, *http.Request) {
|
|||
d.Browser["address"] = ip
|
||||
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, "campaign", c)
|
||||
r = ctx.Set(r, "details", d)
|
||||
|
|
|
@ -2,11 +2,13 @@ package controllers
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gophish/gophish/config"
|
||||
"github.com/gophish/gophish/models"
|
||||
)
|
||||
|
||||
|
@ -50,6 +52,12 @@ func (s *ControllersSuite) reportedEmail(rid string) {
|
|||
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) {
|
||||
resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ps.URL, models.RecipientParameter, rid))
|
||||
s.Nil(err)
|
||||
|
@ -76,6 +84,19 @@ func (s *ControllersSuite) clickLink404(rid string) {
|
|||
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() {
|
||||
campaign := s.getFirstCampaign()
|
||||
result := campaign.Results[0]
|
||||
|
@ -134,13 +155,9 @@ func (s *ControllersSuite) TestNoRecipientID() {
|
|||
|
||||
func (s *ControllersSuite) TestInvalidRecipientID() {
|
||||
rid := "XXXXXXXXXX"
|
||||
resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ps.URL, models.RecipientParameter, rid))
|
||||
s.Nil(err)
|
||||
s.Equal(resp.StatusCode, http.StatusNotFound)
|
||||
|
||||
resp, err = http.Get(fmt.Sprintf("%s/?%s=%s", ps.URL, models.RecipientParameter, rid))
|
||||
s.Nil(err)
|
||||
s.Equal(resp.StatusCode, http.StatusNotFound)
|
||||
s.openEmail404(rid)
|
||||
s.clickLink404(rid)
|
||||
s.reportEmail404(rid)
|
||||
}
|
||||
|
||||
func (s *ControllersSuite) TestCompletedCampaignClick() {
|
||||
|
@ -177,6 +194,7 @@ func (s *ControllersSuite) TestInvalidPreviewID() {
|
|||
bogusRId := fmt.Sprintf("%sbogus", models.PreviewPrefix)
|
||||
s.openEmail404(bogusRId)
|
||||
s.clickLink404(bogusRId)
|
||||
s.reportEmail404(bogusRId)
|
||||
}
|
||||
|
||||
func (s *ControllersSuite) TestPreviewTrack() {
|
||||
|
@ -188,3 +206,25 @@ func (s *ControllersSuite) TestPreviewClick() {
|
|||
req := s.getFirstEmailRequest()
|
||||
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,
|
||||
}
|
||||
ch.Assert(gotEvent, check.DeepEquals, expectedEvent)
|
||||
ch.Assert(result.SendDate, check.Equals, gotEvent.Time)
|
||||
|
||||
ms, err := GetMailLogsByCampaign(campaign.Id)
|
||||
ch.Assert(err, check.Equals, nil)
|
||||
|
|
|
@ -62,6 +62,7 @@ func (r *Result) HandleEmailSent() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.SendDate = event.Time
|
||||
r.Status = EVENT_SENT
|
||||
r.ModifiedDate = event.Time
|
||||
return db.Save(r).Error
|
||||
|
|
Loading…
Reference in New Issue