github.com/edermi/gophish_mods@v0.7.0/controllers/phish.go (about)

     1  package controllers
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  	"net/http"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/gophish/gophish/config"
    12  	ctx "github.com/gophish/gophish/context"
    13  	log "github.com/gophish/gophish/logger"
    14  	"github.com/gophish/gophish/models"
    15  	"github.com/gorilla/mux"
    16  )
    17  
    18  // ErrInvalidRequest is thrown when a request with an invalid structure is
    19  // received
    20  var ErrInvalidRequest = errors.New("Invalid request")
    21  
    22  // ErrCampaignComplete is thrown when an event is received for a campaign that
    23  // has already been marked as complete.
    24  var ErrCampaignComplete = errors.New("Event received on completed campaign")
    25  
    26  // TransparencyResponse is the JSON response provided when a third-party
    27  // makes a request to the transparency handler.
    28  type TransparencyResponse struct {
    29  	Server         string    `json:"server"`
    30  	ContactAddress string    `json:"contact_address"`
    31  	SendDate       time.Time `json:"send_date"`
    32  }
    33  
    34  // TransparencySuffix (when appended to a valid result ID), will cause Gophish
    35  // to return a transparency response.
    36  const TransparencySuffix = "+"
    37  
    38  // CreatePhishingRouter creates the router that handles phishing connections.
    39  func CreatePhishingRouter() http.Handler {
    40  	router := mux.NewRouter()
    41  	fileServer := http.FileServer(UnindexedFileSystem{http.Dir("./static/endpoint/")})
    42  	router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fileServer))
    43  	router.HandleFunc("/track", PhishTracker)
    44  	router.HandleFunc("/robots.txt", RobotsHandler)
    45  	router.HandleFunc("/{path:.*}/track", PhishTracker)
    46  	router.HandleFunc("/{path:.*}/report", PhishReporter)
    47  	router.HandleFunc("/report", PhishReporter)
    48  	router.HandleFunc("/{path:.*}", PhishHandler)
    49  	return router
    50  }
    51  
    52  // PhishTracker tracks emails as they are opened, updating the status for the given Result
    53  func PhishTracker(w http.ResponseWriter, r *http.Request) {
    54  	err, r := setupContext(r)
    55  	if err != nil {
    56  		// Log the error if it wasn't something we can safely ignore
    57  		if err != ErrInvalidRequest && err != ErrCampaignComplete {
    58  			log.Error(err)
    59  		}
    60  		http.NotFound(w, r)
    61  		return
    62  	}
    63  	// Check for a preview
    64  	if _, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
    65  		http.ServeFile(w, r, "static/images/pixel.png")
    66  		return
    67  	}
    68  	rs := ctx.Get(r, "result").(models.Result)
    69  	rid := ctx.Get(r, "rid").(string)
    70  	d := ctx.Get(r, "details").(models.EventDetails)
    71  
    72  	// Check for a transparency request
    73  	if strings.HasSuffix(rid, TransparencySuffix) {
    74  		TransparencyHandler(w, r)
    75  		return
    76  	}
    77  
    78  	err = rs.HandleEmailOpened(d)
    79  	if err != nil {
    80  		log.Error(err)
    81  	}
    82  	http.ServeFile(w, r, "static/images/pixel.png")
    83  }
    84  
    85  // PhishReporter tracks emails as they are reported, updating the status for the given Result
    86  func PhishReporter(w http.ResponseWriter, r *http.Request) {
    87  	err, r := setupContext(r)
    88  	if err != nil {
    89  		// Log the error if it wasn't something we can safely ignore
    90  		if err != ErrInvalidRequest && err != ErrCampaignComplete {
    91  			log.Error(err)
    92  		}
    93  		http.NotFound(w, r)
    94  		return
    95  	}
    96  	// Check for a preview
    97  	if _, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
    98  		w.WriteHeader(http.StatusNoContent)
    99  		return
   100  	}
   101  	rs := ctx.Get(r, "result").(models.Result)
   102  	rid := ctx.Get(r, "rid").(string)
   103  	d := ctx.Get(r, "details").(models.EventDetails)
   104  
   105  	// Check for a transparency request
   106  	if strings.HasSuffix(rid, TransparencySuffix) {
   107  		TransparencyHandler(w, r)
   108  		return
   109  	}
   110  
   111  	err = rs.HandleEmailReport(d)
   112  	if err != nil {
   113  		log.Error(err)
   114  	}
   115  	w.WriteHeader(http.StatusNoContent)
   116  }
   117  
   118  // PhishHandler handles incoming client connections and registers the associated actions performed
   119  // (such as clicked link, etc.)
   120  func PhishHandler(w http.ResponseWriter, r *http.Request) {
   121  	err, r := setupContext(r)
   122  	if err != nil {
   123  		// Log the error if it wasn't something we can safely ignore
   124  		if err != ErrInvalidRequest && err != ErrCampaignComplete {
   125  			log.Error(err)
   126  		}
   127  		http.NotFound(w, r)
   128  		return
   129  	}
   130  	var ptx models.PhishingTemplateContext
   131  	// Check for a preview
   132  	if preview, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
   133  		ptx, err = models.NewPhishingTemplateContext(&preview, preview.BaseRecipient, preview.RId)
   134  		if err != nil {
   135  			log.Error(err)
   136  			http.NotFound(w, r)
   137  			return
   138  		}
   139  		p, err := models.GetPage(preview.PageId, preview.UserId)
   140  		if err != nil {
   141  			log.Error(err)
   142  			http.NotFound(w, r)
   143  			return
   144  		}
   145  		renderPhishResponse(w, r, ptx, p)
   146  		return
   147  	}
   148  	rs := ctx.Get(r, "result").(models.Result)
   149  	rid := ctx.Get(r, "rid").(string)
   150  	c := ctx.Get(r, "campaign").(models.Campaign)
   151  	d := ctx.Get(r, "details").(models.EventDetails)
   152  
   153  	// Check for a transparency request
   154  	if strings.HasSuffix(rid, TransparencySuffix) {
   155  		TransparencyHandler(w, r)
   156  		return
   157  	}
   158  
   159  	p, err := models.GetPage(c.PageId, c.UserId)
   160  	if err != nil {
   161  		log.Error(err)
   162  		http.NotFound(w, r)
   163  		return
   164  	}
   165  	switch {
   166  	case r.Method == "GET":
   167  		err = rs.HandleClickedLink(d)
   168  		if err != nil {
   169  			log.Error(err)
   170  		}
   171  	case r.Method == "POST":
   172  		err = rs.HandleFormSubmit(d)
   173  		if err != nil {
   174  			log.Error(err)
   175  		}
   176  	}
   177  	ptx, err = models.NewPhishingTemplateContext(&c, rs.BaseRecipient, rs.RId)
   178  	if err != nil {
   179  		log.Error(err)
   180  		http.NotFound(w, r)
   181  	}
   182  	renderPhishResponse(w, r, ptx, p)
   183  }
   184  
   185  // renderPhishResponse handles rendering the correct response to the phishing
   186  // connection. This usually involves writing out the page HTML or redirecting
   187  // the user to the correct URL.
   188  func renderPhishResponse(w http.ResponseWriter, r *http.Request, ptx models.PhishingTemplateContext, p models.Page) {
   189  	// If the request was a form submit and a redirect URL was specified, we
   190  	// should send the user to that URL
   191  	if r.Method == "POST" {
   192  		if p.RedirectURL != "" {
   193  			http.Redirect(w, r, p.RedirectURL, 302)
   194  			return
   195  		}
   196  	}
   197  	// Otherwise, we just need to write out the templated HTML
   198  	html, err := models.ExecuteTemplate(p.HTML, ptx)
   199  	if err != nil {
   200  		log.Error(err)
   201  		http.NotFound(w, r)
   202  		return
   203  	}
   204  	w.Write([]byte(html))
   205  }
   206  
   207  // RobotsHandler prevents search engines, etc. from indexing phishing materials
   208  func RobotsHandler(w http.ResponseWriter, r *http.Request) {
   209  	fmt.Fprintln(w, "User-agent: *\nDisallow: /")
   210  }
   211  
   212  // TransparencyHandler returns a TransparencyResponse for the provided result
   213  // and campaign.
   214  func TransparencyHandler(w http.ResponseWriter, r *http.Request) {
   215  	rs := ctx.Get(r, "result").(models.Result)
   216  	tr := &TransparencyResponse{
   217  		Server:         config.ServerName,
   218  		SendDate:       rs.SendDate,
   219  		ContactAddress: config.Conf.ContactAddress,
   220  	}
   221  	JSONResponse(w, tr, http.StatusOK)
   222  }
   223  
   224  // setupContext handles some of the administrative work around receiving a new request, such as checking the result ID, the campaign, etc.
   225  func setupContext(r *http.Request) (error, *http.Request) {
   226  	err := r.ParseForm()
   227  	if err != nil {
   228  		log.Error(err)
   229  		return err, r
   230  	}
   231  	rid := r.Form.Get(models.RecipientParameter)
   232  	if rid == "" {
   233  		return ErrInvalidRequest, r
   234  	}
   235  	// Since we want to support the common case of adding a "+" to indicate a
   236  	// transparency request, we need to take care to handle the case where the
   237  	// request ends with a space, since a "+" is technically reserved for use
   238  	// as a URL encoding of a space.
   239  	if strings.HasSuffix(rid, " ") {
   240  		// We'll trim off the space
   241  		rid = strings.TrimRight(rid, " ")
   242  		// Then we'll add the transparency suffix
   243  		rid = fmt.Sprintf("%s%s", rid, TransparencySuffix)
   244  	}
   245  	// Finally, if this is a transparency request, we'll need to verify that
   246  	// a valid rid has been provided, so we'll look up the result with a
   247  	// trimmed parameter.
   248  	id := strings.TrimSuffix(rid, TransparencySuffix)
   249  	// Check to see if this is a preview or a real result
   250  	if strings.HasPrefix(id, models.PreviewPrefix) {
   251  		rs, err := models.GetEmailRequestByResultId(id)
   252  		if err != nil {
   253  			return err, r
   254  		}
   255  		r = ctx.Set(r, "result", rs)
   256  		return nil, r
   257  	}
   258  	rs, err := models.GetResult(id)
   259  	if err != nil {
   260  		return err, r
   261  	}
   262  	c, err := models.GetCampaign(rs.CampaignId, rs.UserId)
   263  	if err != nil {
   264  		log.Error(err)
   265  		return err, r
   266  	}
   267  	// Don't process events for completed campaigns
   268  	if c.Status == models.CAMPAIGN_COMPLETE {
   269  		return ErrCampaignComplete, r
   270  	}
   271  	ip, _, err := net.SplitHostPort(r.RemoteAddr)
   272  	if err != nil {
   273  		log.Error(err)
   274  		return err, r
   275  	}
   276  	// Respect X-Forwarded headers
   277  	if fips := r.Header.Get("X-Forwarded-For"); fips != "" {
   278  		ip = strings.Split(fips, ", ")[0]
   279  	}
   280  	// Handle post processing such as GeoIP
   281  	err = rs.UpdateGeo(ip)
   282  	if err != nil {
   283  		log.Error(err)
   284  	}
   285  	d := models.EventDetails{
   286  		Payload: r.Form,
   287  		Browser: make(map[string]string),
   288  	}
   289  	d.Browser["address"] = ip
   290  	d.Browser["user-agent"] = r.Header.Get("User-Agent")
   291  
   292  	r = ctx.Set(r, "rid", rid)
   293  	r = ctx.Set(r, "result", rs)
   294  	r = ctx.Set(r, "campaign", c)
   295  	r = ctx.Set(r, "details", d)
   296  	return nil, r
   297  }