🎣 Open-Source Phishing Toolkit
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

236 lines
7.7 KiB

  1. package imap
  2. /* TODO:
  3. * - Have a counter per config for number of consecutive login errors and backoff (e.g if supplied creds are incorrect)
  4. * - Have a DB field "last_login_error" if last login failed
  5. * - DB counter for non-campaign emails that the admin should investigate
  6. * - Add field to User for numner of non-campaign emails reported
  7. */
  8. import (
  9. "bytes"
  10. "context"
  11. "path/filepath"
  12. "regexp"
  13. "strconv"
  14. "strings"
  15. "time"
  16. log "github.com/gophish/gophish/logger"
  17. "github.com/jordan-wright/email"
  18. "github.com/gophish/gophish/models"
  19. )
  20. // Pattern for GoPhish emails e.g ?rid=AbC123
  21. var goPhishRegex = regexp.MustCompile("(\\?rid=(3D)?([A-Za-z0-9]{7}))") // We include the optional quoted-printable 3D at the front, just in case decoding fails
  22. // Monitor is a worker that monitors IMAP servers for reported campaign emails
  23. type Monitor struct {
  24. cancel func()
  25. }
  26. // Monitor.start() checks for campaign emails
  27. // As each account can have its own polling frequency set we need to run one Go routine for
  28. // each, as well as keeping an eye on newly created user accounts.
  29. func (im *Monitor) start(ctx context.Context) {
  30. usermap := make(map[int64]int) // Keep track of running go routines, one per user. We assume incrementing non-repeating UIDs (for the case where users are deleted and re-added).
  31. for {
  32. select {
  33. case <-ctx.Done():
  34. return
  35. default:
  36. dbusers, err := models.GetUsers() //Slice of all user ids. Each user gets their own IMAP monitor routine.
  37. if err != nil {
  38. log.Error(err)
  39. break
  40. }
  41. for _, dbuser := range dbusers {
  42. if _, ok := usermap[dbuser.Id]; !ok { // If we don't currently have a running Go routine for this user, start one.
  43. log.Info("Starting new IMAP monitor for user ", dbuser.Username)
  44. usermap[dbuser.Id] = 1
  45. go monitor(dbuser.Id, ctx)
  46. }
  47. }
  48. time.Sleep(10 * time.Second) // Every ten seconds we check if a new user has been created
  49. }
  50. }
  51. }
  52. // monitor will continuously login to the IMAP settings associated to the supplied user id (if the user account has IMAP settings, and they're enabled.)
  53. // It also verifies the user account exists, and returns if not (for the case of a user being deleted).
  54. func monitor(uid int64, ctx context.Context) {
  55. for {
  56. select {
  57. case <-ctx.Done():
  58. return
  59. default:
  60. // 1. Check if user exists, if not, return.
  61. _, err := models.GetUser(uid)
  62. if err != nil { // Not sure if there's a better way to determine user existence via id.
  63. log.Info("User ", uid, " seems to have been deleted. Stopping IMAP monitor for this user.")
  64. return
  65. }
  66. // 2. Check if user has IMAP settings.
  67. imapSettings, err := models.GetIMAP(uid)
  68. if err != nil {
  69. log.Error(err)
  70. break
  71. }
  72. if len(imapSettings) > 0 {
  73. im := imapSettings[0]
  74. // 3. Check if IMAP is enabled
  75. if im.Enabled {
  76. log.Debug("Checking IMAP for user ", uid, ": ", im.Username, " -> ", im.Host)
  77. checkForNewEmails(im)
  78. time.Sleep((time.Duration(im.IMAPFreq) - 10) * time.Second) // Subtract 10 to compensate for the default sleep of 10 at the bottom
  79. }
  80. }
  81. }
  82. time.Sleep(10 * time.Second)
  83. }
  84. }
  85. // NewMonitor returns a new instance of imap.Monitor
  86. func NewMonitor() *Monitor {
  87. im := &Monitor{}
  88. return im
  89. }
  90. // Start launches the IMAP campaign monitor
  91. func (im *Monitor) Start() error {
  92. log.Info("Starting IMAP monitor manager")
  93. ctx, cancel := context.WithCancel(context.Background()) // ctx is the derivedContext
  94. im.cancel = cancel
  95. go im.start(ctx)
  96. return nil
  97. }
  98. // Shutdown attempts to gracefully shutdown the IMAP monitor.
  99. func (im *Monitor) Shutdown() error {
  100. log.Info("Shutting down IMAP monitor manager")
  101. im.cancel()
  102. return nil
  103. }
  104. // checkForNewEmails logs into an IMAP account and checks unread emails
  105. // for the rid campaign identifier.
  106. func checkForNewEmails(im models.IMAP) {
  107. im.Host = im.Host + ":" + strconv.Itoa(int(im.Port)) // Append port
  108. mailServer := Mailbox{
  109. Host: im.Host,
  110. TLS: im.TLS,
  111. IgnoreCertErrors: im.IgnoreCertErrors,
  112. User: im.Username,
  113. Pwd: im.Password,
  114. Folder: im.Folder,
  115. }
  116. msgs, err := mailServer.GetUnread(true, false)
  117. if err != nil {
  118. log.Error(err)
  119. return
  120. }
  121. // Update last_succesful_login here via im.Host
  122. err = models.SuccessfulLogin(&im)
  123. if len(msgs) > 0 {
  124. log.Debugf("%d new emails for %s", len(msgs), im.Username)
  125. var reportingFailed []uint32 // SeqNums of emails that were unable to be reported to phishing server, mark as unread
  126. var deleteEmails []uint32 // SeqNums of campaign emails. If DeleteReportedCampaignEmail is true, we will delete these
  127. for _, m := range msgs {
  128. // Check if sender is from company's domain, if enabled. TODO: Make this an IMAP filter
  129. if im.RestrictDomain != "" { // e.g domainResitct = widgets.com
  130. splitEmail := strings.Split(m.Email.From, "@")
  131. senderDomain := splitEmail[len(splitEmail)-1]
  132. if senderDomain != im.RestrictDomain {
  133. log.Debug("Ignoring email as not from company domain: ", senderDomain)
  134. continue
  135. }
  136. }
  137. rids, err := matchEmail(m.Email) // Search email Text, HTML, and each attachment for rid parameters
  138. if err != nil {
  139. log.Errorf("Error searching email for rids from user '%s': %s", m.Email.From, err.Error())
  140. continue
  141. }
  142. if len(rids) < 1 {
  143. // In the future this should be an alert in Gophish
  144. log.Infof("User '%s' reported email with subject '%s'. This is not a GoPhish campaign; you should investigate it.", m.Email.From, m.Email.Subject)
  145. }
  146. for rid := range rids {
  147. log.Infof("User '%s' reported email with rid %s", m.Email.From, rid)
  148. result, err := models.GetResult(rid)
  149. if err != nil {
  150. log.Error("Error reporting GoPhish email with rid ", rid, ": ", err.Error())
  151. reportingFailed = append(reportingFailed, m.SeqNum)
  152. continue
  153. }
  154. err = result.HandleEmailReport(models.EventDetails{})
  155. if err != nil {
  156. log.Error("Error updating GoPhish email with rid ", rid, ": ", err.Error())
  157. continue
  158. }
  159. if im.DeleteReportedCampaignEmail == true {
  160. deleteEmails = append(deleteEmails, m.SeqNum)
  161. }
  162. }
  163. }
  164. // Check if any emails were unable to be reported, so we can mark them as unread
  165. if len(reportingFailed) > 0 {
  166. log.Debugf("Marking %d emails as unread as failed to report", len(reportingFailed))
  167. err := mailServer.MarkAsUnread(reportingFailed) // Set emails as unread that we failed to report to GoPhish
  168. if err != nil {
  169. log.Error("Unable to mark emails as unread: ", err.Error())
  170. }
  171. }
  172. // If the DeleteReportedCampaignEmail flag is set, delete reported Gophish campaign emails
  173. if len(deleteEmails) > 0 {
  174. log.Debugf("Deleting %d campaign emails", len(deleteEmails))
  175. err := mailServer.DeleteEmails(deleteEmails) // Delete GoPhish campaign emails.
  176. if err != nil {
  177. log.Error("Failed to delete emails: ", err.Error())
  178. }
  179. }
  180. } else {
  181. log.Debug("No new emails for ", im.Username)
  182. }
  183. }
  184. func checkRIDs(em *email.Email, rids map[string]bool) {
  185. // Check Text and HTML
  186. emailContent := string(em.Text) + string(em.HTML)
  187. for _, r := range goPhishRegex.FindAllStringSubmatch(emailContent, -1) {
  188. newrid := r[len(r)-1]
  189. if !rids[newrid] {
  190. rids[newrid] = true
  191. }
  192. }
  193. }
  194. // returns a slice of gophish rid paramters found in the email HTML, Text, and attachments
  195. func matchEmail(em *email.Email) (map[string]bool, error) {
  196. rids := make(map[string]bool)
  197. checkRIDs(em, rids)
  198. // Next check each attachment
  199. for _, a := range em.Attachments {
  200. ext := filepath.Ext(a.Filename)
  201. if a.Header.Get("Content-Type") == "message/rfc822" || ext == ".eml" {
  202. // Let's decode the email
  203. rawBodyStream := bytes.NewReader(a.Content)
  204. attachmentEmail, err := email.NewEmailFromReader(rawBodyStream)
  205. if err != nil {
  206. return rids, err
  207. }
  208. checkRIDs(attachmentEmail, rids)
  209. }
  210. }
  211. return rids, nil
  212. }