github.com/topsteplocal/gophish@v0.6.0/controllers/phish.go (about)

     1  package controllers
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"html/template"
     9  	"net"
    10  	"net/http"
    11  	"net/mail"
    12  	"net/url"
    13  	"strings"
    14  
    15  	ctx "github.com/gophish/gophish/context"
    16  	log "github.com/gophish/gophish/logger"
    17  	"github.com/gophish/gophish/models"
    18  	"github.com/gorilla/mux"
    19  )
    20  
    21  // ErrInvalidRequest is thrown when a request with an invalid structure is
    22  // received
    23  var ErrInvalidRequest = errors.New("Invalid request")
    24  
    25  // ErrCampaignComplete is thrown when an event is received for a campaign that
    26  // has already been marked as complete.
    27  var ErrCampaignComplete = errors.New("Event received on completed campaign")
    28  
    29  // eventDetails is a struct that wraps common attributes we want to store
    30  // in an event
    31  type eventDetails struct {
    32  	Payload url.Values        `json:"payload"`
    33  	Browser map[string]string `json:"browser"`
    34  }
    35  
    36  // CreatePhishingRouter creates the router that handles phishing connections.
    37  func CreatePhishingRouter() http.Handler {
    38  	router := mux.NewRouter()
    39  	router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/endpoint/"))))
    40  	router.HandleFunc("/track", PhishTracker)
    41  	router.HandleFunc("/robots.txt", RobotsHandler)
    42  	router.HandleFunc("/{path:.*}/track", PhishTracker)
    43  	router.HandleFunc("/{path:.*}/report", PhishReporter)
    44  	router.HandleFunc("/report", PhishReporter)
    45  	router.HandleFunc("/{path:.*}", PhishHandler)
    46  	return router
    47  }
    48  
    49  // PhishTracker tracks emails as they are opened, updating the status for the given Result
    50  func PhishTracker(w http.ResponseWriter, r *http.Request) {
    51  	err, r := setupContext(r)
    52  	if err != nil {
    53  		// Log the error if it wasn't something we can safely ignore
    54  		if err != ErrInvalidRequest && err != ErrCampaignComplete {
    55  			log.Error(err)
    56  		}
    57  		http.NotFound(w, r)
    58  		return
    59  	}
    60  	rs := ctx.Get(r, "result").(models.Result)
    61  	c := ctx.Get(r, "campaign").(models.Campaign)
    62  	rj := ctx.Get(r, "details").([]byte)
    63  	c.AddEvent(models.Event{Email: rs.Email, Message: models.EVENT_OPENED, Details: string(rj)})
    64  	// Don't update the status if the user already clicked the link
    65  	// or submitted data to the campaign
    66  	if rs.Status == models.EVENT_CLICKED || rs.Status == models.EVENT_DATA_SUBMIT {
    67  		http.ServeFile(w, r, "static/images/pixel.png")
    68  		return
    69  	}
    70  	err = rs.UpdateStatus(models.EVENT_OPENED)
    71  	if err != nil {
    72  		log.Error(err)
    73  	}
    74  	http.ServeFile(w, r, "static/images/pixel.png")
    75  }
    76  
    77  // PhishReporter tracks emails as they are reported, updating the status for the given Result
    78  func PhishReporter(w http.ResponseWriter, r *http.Request) {
    79  	err, r := setupContext(r)
    80  	if err != nil {
    81  		// Log the error if it wasn't something we can safely ignore
    82  		if err != ErrInvalidRequest && err != ErrCampaignComplete {
    83  			log.Error(err)
    84  		}
    85  		http.NotFound(w, r)
    86  		return
    87  	}
    88  	rs := ctx.Get(r, "result").(models.Result)
    89  	c := ctx.Get(r, "campaign").(models.Campaign)
    90  	rj := ctx.Get(r, "details").([]byte)
    91  	c.AddEvent(models.Event{Email: rs.Email, Message: models.EVENT_REPORTED, Details: string(rj)})
    92  
    93  	err = rs.UpdateReported(true)
    94  	if err != nil {
    95  		log.Error(err)
    96  	}
    97  	w.WriteHeader(http.StatusNoContent)
    98  }
    99  
   100  // PhishHandler handles incoming client connections and registers the associated actions performed
   101  // (such as clicked link, etc.)
   102  func PhishHandler(w http.ResponseWriter, r *http.Request) {
   103  	err, r := setupContext(r)
   104  	if err != nil {
   105  		// Log the error if it wasn't something we can safely ignore
   106  		if err != ErrInvalidRequest && err != ErrCampaignComplete {
   107  			log.Error(err)
   108  		}
   109  		http.NotFound(w, r)
   110  		return
   111  	}
   112  	rs := ctx.Get(r, "result").(models.Result)
   113  	c := ctx.Get(r, "campaign").(models.Campaign)
   114  	rj := ctx.Get(r, "details").([]byte)
   115  	p, err := models.GetPage(c.PageId, c.UserId)
   116  	if err != nil {
   117  		log.Error(err)
   118  		http.NotFound(w, r)
   119  		return
   120  	}
   121  	switch {
   122  	case r.Method == "GET":
   123  		if rs.Status != models.EVENT_CLICKED && rs.Status != models.EVENT_DATA_SUBMIT {
   124  			rs.UpdateStatus(models.EVENT_CLICKED)
   125  		}
   126  		err = c.AddEvent(models.Event{Email: rs.Email, Message: models.EVENT_CLICKED, Details: string(rj)})
   127  		if err != nil {
   128  			log.Error(err)
   129  		}
   130  	case r.Method == "POST":
   131  		// If data was POST'ed, let's record it
   132  		rs.UpdateStatus(models.EVENT_DATA_SUBMIT)
   133  		// Store the data in an event
   134  		c.AddEvent(models.Event{Email: rs.Email, Message: models.EVENT_DATA_SUBMIT, Details: string(rj)})
   135  		if err != nil {
   136  			log.Error(err)
   137  		}
   138  		// Redirect to the desired page
   139  		if p.RedirectURL != "" {
   140  			http.Redirect(w, r, p.RedirectURL, 302)
   141  			return
   142  		}
   143  	}
   144  	var htmlBuff bytes.Buffer
   145  	tmpl, err := template.New("html_template").Parse(p.HTML)
   146  	if err != nil {
   147  		log.Error(err)
   148  		http.NotFound(w, r)
   149  		return
   150  	}
   151  	f, err := mail.ParseAddress(c.SMTP.FromAddress)
   152  	if err != nil {
   153  		log.Error(err)
   154  	}
   155  	fn := f.Name
   156  	if fn == "" {
   157  		fn = f.Address
   158  	}
   159  
   160  	phishURL, _ := url.Parse(c.URL)
   161  	q := phishURL.Query()
   162  	q.Set(models.RecipientParameter, rs.RId)
   163  	phishURL.RawQuery = q.Encode()
   164  
   165  	rsf := struct {
   166  		models.Result
   167  		URL  string
   168  		From string
   169  	}{
   170  		rs,
   171  		phishURL.String(),
   172  		fn,
   173  	}
   174  	err = tmpl.Execute(&htmlBuff, rsf)
   175  	if err != nil {
   176  		log.Error(err)
   177  		http.NotFound(w, r)
   178  		return
   179  	}
   180  	w.Write(htmlBuff.Bytes())
   181  }
   182  
   183  // RobotsHandler prevents search engines, etc. from indexing phishing materials
   184  func RobotsHandler(w http.ResponseWriter, r *http.Request) {
   185  	fmt.Fprintln(w, "User-agent: *\nDisallow: /")
   186  }
   187  
   188  // setupContext handles some of the administrative work around receiving a new request, such as checking the result ID, the campaign, etc.
   189  func setupContext(r *http.Request) (error, *http.Request) {
   190  	err := r.ParseForm()
   191  	if err != nil {
   192  		log.Error(err)
   193  		return err, r
   194  	}
   195  	id := r.Form.Get(models.RecipientParameter)
   196  	if id == "" {
   197  		return ErrInvalidRequest, r
   198  	}
   199  	rs, err := models.GetResult(id)
   200  	if err != nil {
   201  		return err, r
   202  	}
   203  	c, err := models.GetCampaign(rs.CampaignId, rs.UserId)
   204  	if err != nil {
   205  		log.Error(err)
   206  		return err, r
   207  	}
   208  	// Don't process events for completed campaigns
   209  	if c.Status == models.CAMPAIGN_COMPLETE {
   210  		return ErrCampaignComplete, r
   211  	}
   212  	ip, _, err := net.SplitHostPort(r.RemoteAddr)
   213  	if err != nil {
   214  		log.Error(err)
   215  		return err, r
   216  	}
   217  	// Respect X-Forwarded headers
   218  	if fips := r.Header.Get("X-Forwarded-For"); fips != "" {
   219  		ip = strings.Split(fips, ", ")[0]
   220  	}
   221  	// Handle post processing such as GeoIP
   222  	err = rs.UpdateGeo(ip)
   223  	if err != nil {
   224  		log.Error(err)
   225  	}
   226  	d := eventDetails{
   227  		Payload: r.Form,
   228  		Browser: make(map[string]string),
   229  	}
   230  	d.Browser["address"] = ip
   231  	d.Browser["user-agent"] = r.Header.Get("User-Agent")
   232  	rj, err := json.Marshal(d)
   233  
   234  	r = ctx.Set(r, "result", rs)
   235  	r = ctx.Set(r, "campaign", c)
   236  	r = ctx.Set(r, "details", rj)
   237  	return nil, r
   238  }