adding extended_templates feature

pull/1813/head
Ahmed Khalid 2020-04-16 02:55:43 +02:00
parent 07b46d226a
commit 7debe78745
11 changed files with 102 additions and 43 deletions

View File

@ -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 // connection. This usually involves writing out the page HTML or redirecting
// the user to the correct URL. // the user to the correct URL.
func renderPhishResponse(w http.ResponseWriter, r *http.Request, ptx models.PhishingTemplateContext, p models.Page) { 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 // If the request was a form submit and a redirect URL was specified, we
// should send the user to that URL // should send the user to that URL
if r.Method == "POST" { if r.Method == "POST" {
if p.RedirectURL != "" { if p.RedirectURL != "" {
redirectURL, err := models.ExecuteTemplate(p.RedirectURL, ptx) redirectURL, err := models.ExecuteTemplate(p.RedirectURL, allTemplateParams)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
http.NotFound(w, r) 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 // 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 { if err != nil {
log.Error(err) log.Error(err)
http.NotFound(w, r) http.NotFound(w, r)

View File

@ -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.

View File

@ -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.

View File

@ -552,10 +552,11 @@ func PostCampaign(c *Campaign, uid int64) error {
sendDate := c.generateSendDate(recipientIndex, totalRecipients) sendDate := c.generateSendDate(recipientIndex, totalRecipients)
r := &Result{ r := &Result{
BaseRecipient: BaseRecipient{ BaseRecipient: BaseRecipient{
Email: t.Email, Email: t.Email,
Position: t.Position, Position: t.Position,
FirstName: t.FirstName, FirstName: t.FirstName,
LastName: t.LastName, LastName: t.LastName,
ExtendedTemplate: t.ExtendedTemplate,
}, },
Status: StatusScheduled, Status: StatusScheduled,
CampaignId: c.Id, CampaignId: c.Id,

View File

@ -113,8 +113,10 @@ func (s *EmailRequest) Generate(msg *gomail.Message) error {
if err != nil { if err != nil {
return err 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 { if err != nil {
return err return err
} }
@ -128,12 +130,12 @@ func (s *EmailRequest) Generate(msg *gomail.Message) error {
// Parse the customHeader templates // Parse the customHeader templates
for _, header := range s.SMTP.Headers { for _, header := range s.SMTP.Headers {
key, err := ExecuteTemplate(header.Key, ptx) key, err := ExecuteTemplate(header.Key, allTemplateParams)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
} }
value, err := ExecuteTemplate(header.Value, ptx) value, err := ExecuteTemplate(header.Value, allTemplateParams)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
} }
@ -143,7 +145,7 @@ func (s *EmailRequest) Generate(msg *gomail.Message) error {
} }
// Parse remaining templates // Parse remaining templates
subject, err := ExecuteTemplate(s.Template.Subject, ptx) subject, err := ExecuteTemplate(s.Template.Subject, allTemplateParams)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
} }
@ -154,14 +156,14 @@ func (s *EmailRequest) Generate(msg *gomail.Message) error {
msg.SetHeader("To", s.FormatAddress()) msg.SetHeader("To", s.FormatAddress())
if s.Template.Text != "" { if s.Template.Text != "" {
text, err := ExecuteTemplate(s.Template.Text, ptx) text, err := ExecuteTemplate(s.Template.Text, allTemplateParams)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
} }
msg.SetBody("text/plain", text) msg.SetBody("text/plain", text)
} }
if s.Template.HTML != "" { if s.Template.HTML != "" {
html, err := ExecuteTemplate(s.Template.HTML, ptx) html, err := ExecuteTemplate(s.Template.HTML, allTemplateParams)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
} }

View File

@ -53,10 +53,11 @@ type Target struct {
// BaseRecipient contains the fields for a single recipient. This is the base // BaseRecipient contains the fields for a single recipient. This is the base
// struct used in members of groups and campaign results. // struct used in members of groups and campaign results.
type BaseRecipient struct { type BaseRecipient struct {
Email string `json:"email"` Email string `json:"email"`
FirstName string `json:"first_name"` FirstName string `json:"first_name"`
LastName string `json:"last_name"` LastName string `json:"last_name"`
Position string `json:"position"` Position string `json:"position"`
ExtendedTemplate string `json:"extended_template,omitempty"`
} }
// FormatAddress returns the email address to use in the "To" header of the email // 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, "last_name": target.LastName,
"position": target.Position, "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 err := tx.Model(&target).Where("id = ?", target.Id).Updates(targetInfo).Error
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ 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 // GetTargets performs a many-to-many select to get all the Targets for a Group
func GetTargets(gid int64) ([]Target, error) { func GetTargets(gid int64) ([]Target, error) {
ts := []Target{} 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 return ts, err
} }

View File

@ -179,6 +179,8 @@ func (m *MailLog) Generate(msg *gomail.Message) error {
if err != nil { if err != nil {
return err return err
} }
// Getting all template params.. current + extended.
allTemplateParams := GetAllTemplateParams(ptx)
// Add the transparency headers // Add the transparency headers
msg.SetHeader("X-Mailer", config.ServerName) msg.SetHeader("X-Mailer", config.ServerName)
@ -195,12 +197,12 @@ func (m *MailLog) Generate(msg *gomail.Message) error {
// Parse the customHeader templates // Parse the customHeader templates
for _, header := range c.SMTP.Headers { for _, header := range c.SMTP.Headers {
key, err := ExecuteTemplate(header.Key, ptx) key, err := ExecuteTemplate(header.Key, allTemplateParams)
if err != nil { if err != nil {
log.Warn(err) log.Warn(err)
} }
value, err := ExecuteTemplate(header.Value, ptx) value, err := ExecuteTemplate(header.Value, allTemplateParams)
if err != nil { if err != nil {
log.Warn(err) log.Warn(err)
} }
@ -210,7 +212,7 @@ func (m *MailLog) Generate(msg *gomail.Message) error {
} }
// Parse remaining templates // Parse remaining templates
subject, err := ExecuteTemplate(c.Template.Subject, ptx) subject, err := ExecuteTemplate(c.Template.Subject, allTemplateParams)
if err != nil { if err != nil {
log.Warn(err) log.Warn(err)
} }
@ -221,14 +223,14 @@ func (m *MailLog) Generate(msg *gomail.Message) error {
msg.SetHeader("To", r.FormatAddress()) msg.SetHeader("To", r.FormatAddress())
if c.Template.Text != "" { if c.Template.Text != "" {
text, err := ExecuteTemplate(c.Template.Text, ptx) text, err := ExecuteTemplate(c.Template.Text, allTemplateParams)
if err != nil { if err != nil {
log.Warn(err) log.Warn(err)
} }
msg.SetBody("text/plain", text) msg.SetBody("text/plain", text)
} }
if c.Template.HTML != "" { if c.Template.HTML != "" {
html, err := ExecuteTemplate(c.Template.HTML, ptx) html, err := ExecuteTemplate(c.Template.HTML, allTemplateParams)
if err != nil { if err != nil {
log.Warn(err) log.Warn(err)
} }

View File

@ -79,12 +79,13 @@ func (p *Page) Validate() error {
if p.CapturePasswords && !p.CaptureCredentials { if p.CapturePasswords && !p.CaptureCredentials {
p.CaptureCredentials = true 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 return err
} }
if err := ValidateTemplate(p.RedirectURL); err != nil { if err := ValidateTemplate(p.RedirectURL); err != nil {
return err return err
} }*/
return p.parseHTML() return p.parseHTML()
} }

View File

@ -123,20 +123,21 @@ func (s *ModelsSuite) TestPageValidation(c *check.C) {
err = p.Validate() err = p.Validate()
c.Assert(err, check.Equals, nil) c.Assert(err, check.Equals, nil)
c.Assert(p.CaptureCredentials, check.Equals, true) 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 = `<html>
<head></head>
<body>{{.INVALIDTAG}}</body>
</html>`
err = p.Validate()
c.Assert(err, check.NotNil)
// Validate that if the HTML contains an invalid template tag, that we // Validate that if the RedirectURL contains an invalid template tag, that
// catch it // we catch it
p.HTML = `<html> p.HTML = "valid data"
<head></head> p.RedirectURL = "http://example.com/{{.INVALIDTAG}}"
<body>{{.INVALIDTAG}}</body> err = p.Validate()
</html>` c.Assert(err, check.NotNil)*/
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)
} }

View File

@ -34,12 +34,14 @@ func (t *Template) Validate() error {
case t.Text == "" && t.HTML == "": case t.Text == "" && t.HTML == "":
return ErrTemplateMissingParameter 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 return err
} }
if err := ValidateTemplate(t.Text); err != nil { if err := ValidateTemplate(t.Text); err != nil {
return err return err
} }*/
return nil return nil
} }
@ -109,7 +111,6 @@ func GetTemplateByName(n string, uid int64) (Template, error) {
// PostTemplate creates a new template in the database. // PostTemplate creates a new template in the database.
func PostTemplate(t *Template) error { func PostTemplate(t *Template) error {
// Insert into the DB
if err := t.Validate(); err != nil { if err := t.Validate(); err != nil {
return err return err
} }

View File

@ -2,6 +2,7 @@ package models
import ( import (
"bytes" "bytes"
"encoding/json"
"net/mail" "net/mail"
"net/url" "net/url"
"path" "path"
@ -76,7 +77,8 @@ func NewPhishingTemplateContext(ctx TemplateContext, r BaseRecipient, rid string
// template body and data. // template body and data.
func ExecuteTemplate(text string, data interface{}) (string, error) { func ExecuteTemplate(text string, data interface{}) (string, error) {
buff := bytes.Buffer{} 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 { if err != nil {
return buff.String(), err return buff.String(), err
} }
@ -124,3 +126,31 @@ func ValidateTemplate(text string) error {
} }
return nil 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
}