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 }