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 }