From 7debe7874567138d3b4995a291925d35670eff2e Mon Sep 17 00:00:00 2001 From: Ahmed Khalid Date: Thu, 16 Apr 2020 02:55:43 +0200 Subject: [PATCH] adding extended_templates feature --- controllers/phish.go | 6 ++-- ...20200414000000_0.9.0_extended_template.sql | 7 ++++ ...20200414000000_0.9.0_extended_template.sql | 7 ++++ models/campaign.go | 9 +++--- models/email_request.go | 14 ++++---- models/group.go | 15 ++++++--- models/maillog.go | 12 ++++--- models/page.go | 5 +-- models/page_test.go | 31 +++++++++--------- models/template.go | 7 ++-- models/template_context.go | 32 ++++++++++++++++++- 11 files changed, 102 insertions(+), 43 deletions(-) create mode 100644 db/db_mysql/migrations/20200414000000_0.9.0_extended_template.sql create mode 100644 db/db_sqlite3/migrations/20200414000000_0.9.0_extended_template.sql diff --git a/controllers/phish.go b/controllers/phish.go index 68701dda..265ccf15 100644 --- a/controllers/phish.go +++ b/controllers/phish.go @@ -265,11 +265,13 @@ func (ps *PhishingServer) PhishHandler(w http.ResponseWriter, r *http.Request) { // connection. This usually involves writing out the page HTML or redirecting // the user to the correct URL. func renderPhishResponse(w http.ResponseWriter, r *http.Request, ptx models.PhishingTemplateContext, p models.Page) { + // Getting all template params.. current + extended. + allTemplateParams := models.GetAllTemplateParams(ptx) // If the request was a form submit and a redirect URL was specified, we // should send the user to that URL if r.Method == "POST" { if p.RedirectURL != "" { - redirectURL, err := models.ExecuteTemplate(p.RedirectURL, ptx) + redirectURL, err := models.ExecuteTemplate(p.RedirectURL, allTemplateParams) if err != nil { log.Error(err) http.NotFound(w, r) @@ -280,7 +282,7 @@ func renderPhishResponse(w http.ResponseWriter, r *http.Request, ptx models.Phis } } // Otherwise, we just need to write out the templated HTML - html, err := models.ExecuteTemplate(p.HTML, ptx) + html, err := models.ExecuteTemplate(p.HTML, allTemplateParams) if err != nil { log.Error(err) http.NotFound(w, r) diff --git a/db/db_mysql/migrations/20200414000000_0.9.0_extended_template.sql b/db/db_mysql/migrations/20200414000000_0.9.0_extended_template.sql new file mode 100644 index 00000000..e72c31da --- /dev/null +++ b/db/db_mysql/migrations/20200414000000_0.9.0_extended_template.sql @@ -0,0 +1,7 @@ +-- +goose Up +-- SQL in this section is executed when the migration is applied. +ALTER TABLE `targets` ADD COLUMN extended_template BLOB; +ALTER TABLE `email_requests` ADD COLUMN extended_template BLOB; +ALTER TABLE `results` ADD COLUMN extended_template BLOB; +-- +goose Down +-- SQL in this section is executed when the migration is rolled back. diff --git a/db/db_sqlite3/migrations/20200414000000_0.9.0_extended_template.sql b/db/db_sqlite3/migrations/20200414000000_0.9.0_extended_template.sql new file mode 100644 index 00000000..12344c58 --- /dev/null +++ b/db/db_sqlite3/migrations/20200414000000_0.9.0_extended_template.sql @@ -0,0 +1,7 @@ +-- +goose Up +-- SQL in this section is executed when the migration is applied. +ALTER TABLE targets ADD COLUMN extended_template BLOB; +ALTER TABLE email_requests ADD COLUMN extended_template BLOB; +ALTER TABLE results ADD COLUMN extended_template BLOB; +-- +goose Down +-- SQL in this section is executed when the migration is rolled back. diff --git a/models/campaign.go b/models/campaign.go index 9e8b4770..d8694ddc 100644 --- a/models/campaign.go +++ b/models/campaign.go @@ -552,10 +552,11 @@ func PostCampaign(c *Campaign, uid int64) error { sendDate := c.generateSendDate(recipientIndex, totalRecipients) r := &Result{ BaseRecipient: BaseRecipient{ - Email: t.Email, - Position: t.Position, - FirstName: t.FirstName, - LastName: t.LastName, + Email: t.Email, + Position: t.Position, + FirstName: t.FirstName, + LastName: t.LastName, + ExtendedTemplate: t.ExtendedTemplate, }, Status: StatusScheduled, CampaignId: c.Id, diff --git a/models/email_request.go b/models/email_request.go index e4744c93..0a70ec68 100644 --- a/models/email_request.go +++ b/models/email_request.go @@ -113,8 +113,10 @@ func (s *EmailRequest) Generate(msg *gomail.Message) error { if err != nil { return err } + // Getting all template params.. current + extended. + allTemplateParams := GetAllTemplateParams(ptx) - url, err := ExecuteTemplate(s.URL, ptx) + url, err := ExecuteTemplate(s.URL, allTemplateParams) if err != nil { return err } @@ -128,12 +130,12 @@ func (s *EmailRequest) Generate(msg *gomail.Message) error { // Parse the customHeader templates for _, header := range s.SMTP.Headers { - key, err := ExecuteTemplate(header.Key, ptx) + key, err := ExecuteTemplate(header.Key, allTemplateParams) if err != nil { log.Error(err) } - value, err := ExecuteTemplate(header.Value, ptx) + value, err := ExecuteTemplate(header.Value, allTemplateParams) if err != nil { log.Error(err) } @@ -143,7 +145,7 @@ func (s *EmailRequest) Generate(msg *gomail.Message) error { } // Parse remaining templates - subject, err := ExecuteTemplate(s.Template.Subject, ptx) + subject, err := ExecuteTemplate(s.Template.Subject, allTemplateParams) if err != nil { log.Error(err) } @@ -154,14 +156,14 @@ func (s *EmailRequest) Generate(msg *gomail.Message) error { msg.SetHeader("To", s.FormatAddress()) if s.Template.Text != "" { - text, err := ExecuteTemplate(s.Template.Text, ptx) + text, err := ExecuteTemplate(s.Template.Text, allTemplateParams) if err != nil { log.Error(err) } msg.SetBody("text/plain", text) } if s.Template.HTML != "" { - html, err := ExecuteTemplate(s.Template.HTML, ptx) + html, err := ExecuteTemplate(s.Template.HTML, allTemplateParams) if err != nil { log.Error(err) } diff --git a/models/group.go b/models/group.go index 9a5ef39e..0a1543fa 100644 --- a/models/group.go +++ b/models/group.go @@ -53,10 +53,11 @@ type Target struct { // BaseRecipient contains the fields for a single recipient. This is the base // struct used in members of groups and campaign results. type BaseRecipient struct { - Email string `json:"email"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Position string `json:"position"` + Email string `json:"email"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Position string `json:"position"` + ExtendedTemplate string `json:"extended_template,omitempty"` } // FormatAddress returns the email address to use in the "To" header of the email @@ -348,6 +349,10 @@ func UpdateTarget(tx *gorm.DB, target Target) error { "last_name": target.LastName, "position": target.Position, } + // handling front end overrides..extended_template is not sent from front end. + if target.ExtendedTemplate != "" { + targetInfo["extended_template"] = target.ExtendedTemplate + } err := tx.Model(&target).Where("id = ?", target.Id).Updates(targetInfo).Error if err != nil { log.WithFields(logrus.Fields{ @@ -360,6 +365,6 @@ func UpdateTarget(tx *gorm.DB, target Target) error { // GetTargets performs a many-to-many select to get all the Targets for a Group func GetTargets(gid int64) ([]Target, error) { ts := []Target{} - err := db.Table("targets").Select("targets.id, targets.email, targets.first_name, targets.last_name, targets.position").Joins("left join group_targets gt ON targets.id = gt.target_id").Where("gt.group_id=?", gid).Scan(&ts).Error + err := db.Table("targets").Select("targets.id, targets.email, targets.first_name, targets.last_name, targets.position,targets.extended_template").Joins("left join group_targets gt ON targets.id = gt.target_id").Where("gt.group_id=?", gid).Scan(&ts).Error return ts, err } diff --git a/models/maillog.go b/models/maillog.go index 082ddceb..c208fb1d 100644 --- a/models/maillog.go +++ b/models/maillog.go @@ -179,6 +179,8 @@ func (m *MailLog) Generate(msg *gomail.Message) error { if err != nil { return err } + // Getting all template params.. current + extended. + allTemplateParams := GetAllTemplateParams(ptx) // Add the transparency headers msg.SetHeader("X-Mailer", config.ServerName) @@ -195,12 +197,12 @@ func (m *MailLog) Generate(msg *gomail.Message) error { // Parse the customHeader templates for _, header := range c.SMTP.Headers { - key, err := ExecuteTemplate(header.Key, ptx) + key, err := ExecuteTemplate(header.Key, allTemplateParams) if err != nil { log.Warn(err) } - value, err := ExecuteTemplate(header.Value, ptx) + value, err := ExecuteTemplate(header.Value, allTemplateParams) if err != nil { log.Warn(err) } @@ -210,7 +212,7 @@ func (m *MailLog) Generate(msg *gomail.Message) error { } // Parse remaining templates - subject, err := ExecuteTemplate(c.Template.Subject, ptx) + subject, err := ExecuteTemplate(c.Template.Subject, allTemplateParams) if err != nil { log.Warn(err) } @@ -221,14 +223,14 @@ func (m *MailLog) Generate(msg *gomail.Message) error { msg.SetHeader("To", r.FormatAddress()) if c.Template.Text != "" { - text, err := ExecuteTemplate(c.Template.Text, ptx) + text, err := ExecuteTemplate(c.Template.Text, allTemplateParams) if err != nil { log.Warn(err) } msg.SetBody("text/plain", text) } if c.Template.HTML != "" { - html, err := ExecuteTemplate(c.Template.HTML, ptx) + html, err := ExecuteTemplate(c.Template.HTML, allTemplateParams) if err != nil { log.Warn(err) } diff --git a/models/page.go b/models/page.go index 30da2179..cd3540a0 100644 --- a/models/page.go +++ b/models/page.go @@ -79,12 +79,13 @@ func (p *Page) Validate() error { if p.CapturePasswords && !p.CaptureCredentials { p.CaptureCredentials = true } - if err := ValidateTemplate(p.HTML); err != nil { + // bypass this.. since we can't predict with all extended params in advance. + /*if err := ValidateTemplate(p.HTML); err != nil { return err } if err := ValidateTemplate(p.RedirectURL); err != nil { return err - } + }*/ return p.parseHTML() } diff --git a/models/page_test.go b/models/page_test.go index 8152081c..10a86dfa 100644 --- a/models/page_test.go +++ b/models/page_test.go @@ -123,20 +123,21 @@ func (s *ModelsSuite) TestPageValidation(c *check.C) { err = p.Validate() c.Assert(err, check.Equals, nil) c.Assert(p.CaptureCredentials, check.Equals, true) + // bypassing..no longer needed + /* + // Validate that if the HTML contains an invalid template tag, that we + // catch it + p.HTML = ` + + {{.INVALIDTAG}} + ` + err = p.Validate() + c.Assert(err, check.NotNil) - // Validate that if the HTML contains an invalid template tag, that we - // catch it - p.HTML = ` - - {{.INVALIDTAG}} - ` - err = p.Validate() - c.Assert(err, check.NotNil) - - // Validate that if the RedirectURL contains an invalid template tag, that - // we catch it - p.HTML = "valid data" - p.RedirectURL = "http://example.com/{{.INVALIDTAG}}" - err = p.Validate() - c.Assert(err, check.NotNil) + // Validate that if the RedirectURL contains an invalid template tag, that + // we catch it + p.HTML = "valid data" + p.RedirectURL = "http://example.com/{{.INVALIDTAG}}" + err = p.Validate() + c.Assert(err, check.NotNil)*/ } diff --git a/models/template.go b/models/template.go index 05d4a64c..8ff43207 100644 --- a/models/template.go +++ b/models/template.go @@ -34,12 +34,14 @@ func (t *Template) Validate() error { case t.Text == "" && t.HTML == "": return ErrTemplateMissingParameter } - if err := ValidateTemplate(t.HTML); err != nil { + + // bypass this.. since we can't predict all extended params in advance. + /*if err := ValidateTemplate(t.HTML); err != nil { return err } if err := ValidateTemplate(t.Text); err != nil { return err - } + }*/ return nil } @@ -109,7 +111,6 @@ func GetTemplateByName(n string, uid int64) (Template, error) { // PostTemplate creates a new template in the database. func PostTemplate(t *Template) error { - // Insert into the DB if err := t.Validate(); err != nil { return err } diff --git a/models/template_context.go b/models/template_context.go index ed3751d1..4dce90b0 100644 --- a/models/template_context.go +++ b/models/template_context.go @@ -2,6 +2,7 @@ package models import ( "bytes" + "encoding/json" "net/mail" "net/url" "path" @@ -76,7 +77,8 @@ func NewPhishingTemplateContext(ctx TemplateContext, r BaseRecipient, rid string // template body and data. func ExecuteTemplate(text string, data interface{}) (string, error) { buff := bytes.Buffer{} - tmpl, err := template.New("template").Parse(text) + // replacing template params with no corresponding map keys with empty string. + tmpl, err := template.New("template").Option("missingkey=zero").Parse(text) if err != nil { return buff.String(), err } @@ -124,3 +126,31 @@ func ValidateTemplate(text string) error { } return nil } + +// GetAllTemplateParams merges the PhishingTemplateContext with the extended_parameters +// if extended_parameters are valid JSON +func GetAllTemplateParams(ptx PhishingTemplateContext) map[string]string { + currentTemplateParams := map[string]string{ + "From": ptx.From, + "URL": ptx.URL, + "Tracker": ptx.Tracker, + "TrackingURL": ptx.TrackingURL, + "RId": ptx.RId, + "BaseURL": ptx.BaseURL, + "Email": ptx.BaseRecipient.Email, + "FirstName": ptx.BaseRecipient.FirstName, + "LastName": ptx.BaseRecipient.LastName, + "Position": ptx.BaseRecipient.Position, + } + extendedTemplateParams := map[string]string{} + err := json.Unmarshal([]byte(ptx.BaseRecipient.ExtendedTemplate), &extendedTemplateParams) + + if err == nil { + // only merge if we have a valid JSON in extended_parameters + for k, v := range extendedTemplateParams { + currentTemplateParams[k] = v + } + } + return currentTemplateParams + +}