2017-06-09 04:41:38 +00:00
|
|
|
package controllers
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
ctx "github.com/gophish/gophish/context"
|
2018-05-04 00:07:41 +00:00
|
|
|
log "github.com/gophish/gophish/logger"
|
2017-06-09 04:41:38 +00:00
|
|
|
"github.com/gophish/gophish/models"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ErrInvalidRequest is thrown when a request with an invalid structure is
|
|
|
|
// received
|
|
|
|
var ErrInvalidRequest = errors.New("Invalid request")
|
|
|
|
|
|
|
|
// ErrCampaignComplete is thrown when an event is received for a campaign that
|
|
|
|
// has already been marked as complete.
|
|
|
|
var ErrCampaignComplete = errors.New("Event received on completed campaign")
|
|
|
|
|
|
|
|
// CreatePhishingRouter creates the router that handles phishing connections.
|
|
|
|
func CreatePhishingRouter() http.Handler {
|
|
|
|
router := mux.NewRouter()
|
2018-05-24 04:03:48 +00:00
|
|
|
fileServer := http.FileServer(UnindexedFileSystem{http.Dir("./static/endpoint/")})
|
|
|
|
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fileServer))
|
2017-06-09 04:41:38 +00:00
|
|
|
router.HandleFunc("/track", PhishTracker)
|
|
|
|
router.HandleFunc("/robots.txt", RobotsHandler)
|
|
|
|
router.HandleFunc("/{path:.*}/track", PhishTracker)
|
2018-03-19 03:03:00 +00:00
|
|
|
router.HandleFunc("/{path:.*}/report", PhishReporter)
|
|
|
|
router.HandleFunc("/report", PhishReporter)
|
2017-06-09 04:41:38 +00:00
|
|
|
router.HandleFunc("/{path:.*}", PhishHandler)
|
|
|
|
return router
|
|
|
|
}
|
|
|
|
|
|
|
|
// PhishTracker tracks emails as they are opened, updating the status for the given Result
|
|
|
|
func PhishTracker(w http.ResponseWriter, r *http.Request) {
|
|
|
|
err, r := setupContext(r)
|
|
|
|
if err != nil {
|
|
|
|
// Log the error if it wasn't something we can safely ignore
|
|
|
|
if err != ErrInvalidRequest && err != ErrCampaignComplete {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
2018-06-09 02:20:52 +00:00
|
|
|
// Check for a preview
|
|
|
|
if _, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
|
|
|
|
http.ServeFile(w, r, "static/images/pixel.png")
|
|
|
|
return
|
|
|
|
}
|
2017-06-09 04:41:38 +00:00
|
|
|
rs := ctx.Get(r, "result").(models.Result)
|
2018-05-27 02:26:34 +00:00
|
|
|
d := ctx.Get(r, "details").(models.EventDetails)
|
|
|
|
err = rs.HandleEmailOpened(d)
|
2017-06-09 04:41:38 +00:00
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
http.ServeFile(w, r, "static/images/pixel.png")
|
|
|
|
}
|
|
|
|
|
2018-03-19 03:03:00 +00:00
|
|
|
// PhishReporter tracks emails as they are reported, updating the status for the given Result
|
|
|
|
func PhishReporter(w http.ResponseWriter, r *http.Request) {
|
|
|
|
err, r := setupContext(r)
|
|
|
|
if err != nil {
|
|
|
|
// Log the error if it wasn't something we can safely ignore
|
|
|
|
if err != ErrInvalidRequest && err != ErrCampaignComplete {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2018-03-19 03:03:00 +00:00
|
|
|
}
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
2018-06-09 02:20:52 +00:00
|
|
|
// Check for a preview
|
|
|
|
if _, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
return
|
|
|
|
}
|
2018-03-19 03:03:00 +00:00
|
|
|
rs := ctx.Get(r, "result").(models.Result)
|
2018-05-27 02:26:34 +00:00
|
|
|
d := ctx.Get(r, "details").(models.EventDetails)
|
2018-03-19 03:03:00 +00:00
|
|
|
|
2018-05-27 02:26:34 +00:00
|
|
|
err = rs.HandleEmailReport(d)
|
2018-03-19 03:03:00 +00:00
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2018-03-19 03:03:00 +00:00
|
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
}
|
|
|
|
|
2017-06-09 04:41:38 +00:00
|
|
|
// PhishHandler handles incoming client connections and registers the associated actions performed
|
|
|
|
// (such as clicked link, etc.)
|
|
|
|
func PhishHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
err, r := setupContext(r)
|
|
|
|
if err != nil {
|
|
|
|
// Log the error if it wasn't something we can safely ignore
|
|
|
|
if err != ErrInvalidRequest && err != ErrCampaignComplete {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
2018-06-09 02:20:52 +00:00
|
|
|
var ptx models.PhishingTemplateContext
|
|
|
|
// Check for a preview
|
|
|
|
if preview, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
|
|
|
|
ptx, err = models.NewPhishingTemplateContext(&preview, preview.BaseRecipient, preview.RId)
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err)
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p, err := models.GetPage(preview.PageId, preview.UserId)
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err)
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
renderPhishResponse(w, r, ptx, p)
|
|
|
|
return
|
|
|
|
}
|
2017-06-09 04:41:38 +00:00
|
|
|
rs := ctx.Get(r, "result").(models.Result)
|
|
|
|
c := ctx.Get(r, "campaign").(models.Campaign)
|
2018-05-27 02:26:34 +00:00
|
|
|
d := ctx.Get(r, "details").(models.EventDetails)
|
2017-06-09 04:41:38 +00:00
|
|
|
p, err := models.GetPage(c.PageId, c.UserId)
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2018-03-23 02:29:07 +00:00
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
switch {
|
|
|
|
case r.Method == "GET":
|
2018-05-27 02:26:34 +00:00
|
|
|
err = rs.HandleClickedLink(d)
|
2017-06-09 04:41:38 +00:00
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
case r.Method == "POST":
|
2018-05-27 02:26:34 +00:00
|
|
|
err = rs.HandleFormSubmit(d)
|
2017-06-09 04:41:38 +00:00
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
}
|
2018-06-09 02:20:52 +00:00
|
|
|
ptx, err = models.NewPhishingTemplateContext(&c, rs.BaseRecipient, rs.RId)
|
2017-06-09 04:41:38 +00:00
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
http.NotFound(w, r)
|
|
|
|
}
|
2018-06-09 02:20:52 +00:00
|
|
|
renderPhishResponse(w, r, ptx, p)
|
|
|
|
}
|
2018-02-23 05:02:27 +00:00
|
|
|
|
2018-06-09 02:20:52 +00:00
|
|
|
// renderPhishResponse handles rendering the correct response to the phishing
|
|
|
|
// connection. This usually involves writing out the page HTML or redirecting
|
|
|
|
// the user to the correct URL.
|
|
|
|
func renderPhishResponse(w http.ResponseWriter, r *http.Request, ptx models.PhishingTemplateContext, p models.Page) {
|
|
|
|
// If the request was a form submit and a redirect URL was specified, we
|
|
|
|
// should send the user to that URL
|
|
|
|
if r.Method == "POST" {
|
|
|
|
if p.RedirectURL != "" {
|
|
|
|
http.Redirect(w, r, p.RedirectURL, 302)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Otherwise, we just need to write out the templated HTML
|
|
|
|
html, err := models.ExecuteTemplate(p.HTML, ptx)
|
2017-06-09 04:41:38 +00:00
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
http.NotFound(w, r)
|
2018-03-23 02:29:07 +00:00
|
|
|
return
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
2018-06-09 02:20:52 +00:00
|
|
|
w.Write([]byte(html))
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// RobotsHandler prevents search engines, etc. from indexing phishing materials
|
|
|
|
func RobotsHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
fmt.Fprintln(w, "User-agent: *\nDisallow: /")
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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()
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
return err, r
|
|
|
|
}
|
2018-02-23 05:02:27 +00:00
|
|
|
id := r.Form.Get(models.RecipientParameter)
|
2017-06-09 04:41:38 +00:00
|
|
|
if id == "" {
|
|
|
|
return ErrInvalidRequest, r
|
|
|
|
}
|
2018-06-09 02:20:52 +00:00
|
|
|
// Check to see if this is a preview or a real result
|
|
|
|
if strings.HasPrefix(id, models.PreviewPrefix) {
|
|
|
|
rs, err := models.GetEmailRequestByResultId(id)
|
|
|
|
if err != nil {
|
|
|
|
return err, r
|
|
|
|
}
|
|
|
|
r = ctx.Set(r, "result", rs)
|
|
|
|
return nil, r
|
|
|
|
}
|
2017-06-09 04:41:38 +00:00
|
|
|
rs, err := models.GetResult(id)
|
|
|
|
if err != nil {
|
|
|
|
return err, r
|
|
|
|
}
|
|
|
|
c, err := models.GetCampaign(rs.CampaignId, rs.UserId)
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
return err, r
|
|
|
|
}
|
|
|
|
// Don't process events for completed campaigns
|
|
|
|
if c.Status == models.CAMPAIGN_COMPLETE {
|
|
|
|
return ErrCampaignComplete, r
|
|
|
|
}
|
|
|
|
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
return err, r
|
|
|
|
}
|
|
|
|
// Respect X-Forwarded headers
|
|
|
|
if fips := r.Header.Get("X-Forwarded-For"); fips != "" {
|
|
|
|
ip = strings.Split(fips, ", ")[0]
|
|
|
|
}
|
|
|
|
// Handle post processing such as GeoIP
|
|
|
|
err = rs.UpdateGeo(ip)
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
2018-05-27 02:26:34 +00:00
|
|
|
d := models.EventDetails{
|
2017-06-09 04:41:38 +00:00
|
|
|
Payload: r.Form,
|
|
|
|
Browser: make(map[string]string),
|
|
|
|
}
|
|
|
|
d.Browser["address"] = ip
|
|
|
|
d.Browser["user-agent"] = r.Header.Get("User-Agent")
|
|
|
|
|
|
|
|
r = ctx.Set(r, "result", rs)
|
|
|
|
r = ctx.Set(r, "campaign", c)
|
2018-05-27 02:26:34 +00:00
|
|
|
r = ctx.Set(r, "details", d)
|
2017-06-09 04:41:38 +00:00
|
|
|
return nil, r
|
|
|
|
}
|