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 }