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