2014-06-03 18:27:20 +00:00
|
|
|
package models
|
|
|
|
|
2016-02-22 03:09:14 +00:00
|
|
|
import (
|
2017-12-09 21:42:07 +00:00
|
|
|
"crypto/tls"
|
2016-02-21 02:53:53 +00:00
|
|
|
"errors"
|
2016-02-22 04:12:47 +00:00
|
|
|
"net/mail"
|
2017-12-09 21:42:07 +00:00
|
|
|
"os"
|
2022-11-29 16:41:10 +00:00
|
|
|
"regexp"
|
2016-05-30 19:53:32 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2016-02-21 02:53:53 +00:00
|
|
|
"time"
|
2017-02-20 00:43:08 +00:00
|
|
|
|
2017-12-09 21:42:07 +00:00
|
|
|
"github.com/gophish/gomail"
|
Implement SSRF Mitigations (#1940)
Initial commit of SSRF mitigations.
This fixes #1908 by creating a *net.Dialer which restricts outbound connections to only allowed IP ranges. This implementation is based on the blog post at https://www.agwa.name/blog/post/preventing_server_side_request_forgery_in_golang
To keep things backwards compatible, by default we'll only block connections to 169.254.169.254, the link-local IP address commonly used in cloud environments to retrieve metadata about the running instance. For other internal addresses (e.g. localhost or RFC 1918 addresses), it's assumed that those are available to Gophish.
To support more secure environments, we introduce the `allowed_internal_hosts` configuration option where an admin can set one or more IP ranges in CIDR format. If addresses are specified here, then all internal connections will be blocked except to these hosts.
There are various bits about this approach I don't really like. For example, since various packages all need this functionality, I had to make the RestrictedDialer a global singleton rather than a dependency off of, say, the admin server. Additionally, since webhooks are implemented via a singleton, I had to introduce a new function, `SetTransport`.
Finally, I had to make an update in the gomail package to support a custom net.Dialer.
2020-08-20 14:36:18 +00:00
|
|
|
"github.com/gophish/gophish/dialer"
|
2018-05-04 00:07:41 +00:00
|
|
|
log "github.com/gophish/gophish/logger"
|
2017-12-09 21:42:07 +00:00
|
|
|
"github.com/gophish/gophish/mailer"
|
2017-02-20 00:43:08 +00:00
|
|
|
"github.com/jinzhu/gorm"
|
2016-02-21 02:53:53 +00:00
|
|
|
)
|
2016-02-02 00:36:59 +00:00
|
|
|
|
2017-12-09 21:42:07 +00:00
|
|
|
// Dialer is a wrapper around a standard gomail.Dialer in order
|
|
|
|
// to implement the mailer.Dialer interface. This allows us to better
|
|
|
|
// separate the mailer package as opposed to forcing a connection
|
|
|
|
// between mailer and gomail.
|
|
|
|
type Dialer struct {
|
|
|
|
*gomail.Dialer
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dial wraps the gomail dialer's Dial command
|
|
|
|
func (d *Dialer) Dial() (mailer.Sender, error) {
|
|
|
|
return d.Dialer.Dial()
|
|
|
|
}
|
|
|
|
|
2015-02-07 20:31:41 +00:00
|
|
|
// SMTP contains the attributes needed to handle the sending of campaign emails
|
2014-06-03 18:27:20 +00:00
|
|
|
type SMTP struct {
|
2016-02-22 03:09:14 +00:00
|
|
|
Id int64 `json:"id" gorm:"column:id; primary_key:yes"`
|
|
|
|
UserId int64 `json:"-" gorm:"column:user_id"`
|
|
|
|
Interface string `json:"interface_type" gorm:"column:interface_type"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Host string `json:"host"`
|
|
|
|
Username string `json:"username,omitempty"`
|
|
|
|
Password string `json:"password,omitempty"`
|
|
|
|
FromAddress string `json:"from_address"`
|
|
|
|
IgnoreCertErrors bool `json:"ignore_cert_errors"`
|
2017-02-20 00:43:08 +00:00
|
|
|
Headers []Header `json:"headers"`
|
2016-02-22 03:09:14 +00:00
|
|
|
ModifiedDate time.Time `json:"modified_date"`
|
2014-06-03 18:27:20 +00:00
|
|
|
}
|
|
|
|
|
2017-02-20 00:43:08 +00:00
|
|
|
// Header contains the fields and methods for a sending profile to have
|
|
|
|
// custom headers
|
|
|
|
type Header struct {
|
|
|
|
Id int64 `json:"-"`
|
|
|
|
SMTPId int64 `json:"-"`
|
|
|
|
Key string `json:"key"`
|
|
|
|
Value string `json:"value"`
|
|
|
|
}
|
|
|
|
|
2016-02-02 00:36:59 +00:00
|
|
|
// ErrFromAddressNotSpecified is thrown when there is no "From" address
|
|
|
|
// specified in the SMTP configuration
|
|
|
|
var ErrFromAddressNotSpecified = errors.New("No From Address specified")
|
|
|
|
|
2022-11-29 16:41:10 +00:00
|
|
|
// ErrInvalidFromAddress is thrown when the SMTP From field in the sending
|
|
|
|
// profiles containes a value that is not an email address
|
|
|
|
var ErrInvalidFromAddress = errors.New("Invalid SMTP From address because it is not an email address")
|
|
|
|
|
2016-02-02 00:36:59 +00:00
|
|
|
// ErrHostNotSpecified is thrown when there is no Host specified
|
|
|
|
// in the SMTP configuration
|
|
|
|
var ErrHostNotSpecified = errors.New("No SMTP Host specified")
|
|
|
|
|
2016-05-30 19:53:32 +00:00
|
|
|
// ErrInvalidHost indicates that the SMTP server string is invalid
|
|
|
|
var ErrInvalidHost = errors.New("Invalid SMTP server address")
|
|
|
|
|
2015-02-07 20:31:41 +00:00
|
|
|
// TableName specifies the database tablename for Gorm to use
|
|
|
|
func (s SMTP) TableName() string {
|
|
|
|
return "smtp"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate ensures that SMTP configs/connections are valid
|
2016-02-02 00:36:59 +00:00
|
|
|
func (s *SMTP) Validate() error {
|
2014-06-03 18:27:20 +00:00
|
|
|
switch {
|
|
|
|
case s.FromAddress == "":
|
2016-02-02 00:36:59 +00:00
|
|
|
return ErrFromAddressNotSpecified
|
2014-06-05 04:54:46 +00:00
|
|
|
case s.Host == "":
|
2016-02-02 00:36:59 +00:00
|
|
|
return ErrHostNotSpecified
|
2022-11-29 16:41:10 +00:00
|
|
|
case !validateFromAddress(s.FromAddress):
|
|
|
|
return ErrInvalidFromAddress
|
2014-06-03 18:27:20 +00:00
|
|
|
}
|
2016-02-22 04:12:47 +00:00
|
|
|
_, err := mail.ParseAddress(s.FromAddress)
|
2016-05-30 19:53:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Make sure addr is in host:port format
|
|
|
|
hp := strings.Split(s.Host, ":")
|
|
|
|
if len(hp) > 2 {
|
|
|
|
return ErrInvalidHost
|
|
|
|
} else if len(hp) < 2 {
|
|
|
|
hp = append(hp, "25")
|
|
|
|
}
|
|
|
|
_, err = strconv.Atoi(hp[1])
|
|
|
|
if err != nil {
|
|
|
|
return ErrInvalidHost
|
|
|
|
}
|
2016-02-22 04:12:47 +00:00
|
|
|
return err
|
2014-06-03 18:27:20 +00:00
|
|
|
}
|
2016-02-21 02:53:53 +00:00
|
|
|
|
2022-11-29 16:41:10 +00:00
|
|
|
// validateFromAddress validates
|
|
|
|
func validateFromAddress(email string) bool {
|
2022-12-16 17:04:55 +00:00
|
|
|
r, _ := regexp.Compile("^([a-zA-Z0-9_\\-\\.]+)@([a-zA-Z0-9_\\-\\.]+)\\.([a-zA-Z]{2,18})$")
|
2022-11-29 16:41:10 +00:00
|
|
|
return r.MatchString(email)
|
|
|
|
}
|
|
|
|
|
2017-12-09 21:42:07 +00:00
|
|
|
// GetDialer returns a dialer for the given SMTP profile
|
|
|
|
func (s *SMTP) GetDialer() (mailer.Dialer, error) {
|
|
|
|
// Setup the message and dial
|
|
|
|
hp := strings.Split(s.Host, ":")
|
|
|
|
if len(hp) < 2 {
|
|
|
|
hp = append(hp, "25")
|
|
|
|
}
|
2020-01-21 13:21:56 +00:00
|
|
|
host := hp[0]
|
2017-12-09 21:42:07 +00:00
|
|
|
// Any issues should have been caught in validation, but we'll
|
|
|
|
// double check here.
|
|
|
|
port, err := strconv.Atoi(hp[1])
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-12-09 21:42:07 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
Implement SSRF Mitigations (#1940)
Initial commit of SSRF mitigations.
This fixes #1908 by creating a *net.Dialer which restricts outbound connections to only allowed IP ranges. This implementation is based on the blog post at https://www.agwa.name/blog/post/preventing_server_side_request_forgery_in_golang
To keep things backwards compatible, by default we'll only block connections to 169.254.169.254, the link-local IP address commonly used in cloud environments to retrieve metadata about the running instance. For other internal addresses (e.g. localhost or RFC 1918 addresses), it's assumed that those are available to Gophish.
To support more secure environments, we introduce the `allowed_internal_hosts` configuration option where an admin can set one or more IP ranges in CIDR format. If addresses are specified here, then all internal connections will be blocked except to these hosts.
There are various bits about this approach I don't really like. For example, since various packages all need this functionality, I had to make the RestrictedDialer a global singleton rather than a dependency off of, say, the admin server. Additionally, since webhooks are implemented via a singleton, I had to introduce a new function, `SetTransport`.
Finally, I had to make an update in the gomail package to support a custom net.Dialer.
2020-08-20 14:36:18 +00:00
|
|
|
dialer := dialer.Dialer()
|
|
|
|
d := gomail.NewWithDialer(dialer, host, port, s.Username, s.Password)
|
2017-12-09 21:42:07 +00:00
|
|
|
d.TLSConfig = &tls.Config{
|
2020-01-21 13:21:56 +00:00
|
|
|
ServerName: host,
|
2017-12-09 21:42:07 +00:00
|
|
|
InsecureSkipVerify: s.IgnoreCertErrors,
|
|
|
|
}
|
|
|
|
hostname, err := os.Hostname()
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-12-09 21:42:07 +00:00
|
|
|
hostname = "localhost"
|
|
|
|
}
|
|
|
|
d.LocalName = hostname
|
|
|
|
return &Dialer{d}, err
|
|
|
|
}
|
|
|
|
|
2016-02-21 02:53:53 +00:00
|
|
|
// GetSMTPs returns the SMTPs owned by the given user.
|
|
|
|
func GetSMTPs(uid int64) ([]SMTP, error) {
|
|
|
|
ss := []SMTP{}
|
|
|
|
err := db.Where("user_id=?", uid).Find(&ss).Error
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-02-20 00:43:08 +00:00
|
|
|
return ss, err
|
2016-02-21 02:53:53 +00:00
|
|
|
}
|
2017-12-09 21:42:07 +00:00
|
|
|
for i := range ss {
|
2017-02-20 00:43:08 +00:00
|
|
|
err = db.Where("smtp_id=?", ss[i].Id).Find(&ss[i].Headers).Error
|
|
|
|
if err != nil && err != gorm.ErrRecordNotFound {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-02-20 00:43:08 +00:00
|
|
|
return ss, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ss, nil
|
2016-02-21 02:53:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetSMTP returns the SMTP, if it exists, specified by the given id and user_id.
|
|
|
|
func GetSMTP(id int64, uid int64) (SMTP, error) {
|
|
|
|
s := SMTP{}
|
|
|
|
err := db.Where("user_id=? and id=?", uid, id).Find(&s).Error
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2018-10-07 17:37:15 +00:00
|
|
|
return s, err
|
2016-02-21 02:53:53 +00:00
|
|
|
}
|
2017-02-20 00:43:08 +00:00
|
|
|
err = db.Where("smtp_id=?", s.Id).Find(&s.Headers).Error
|
|
|
|
if err != nil && err != gorm.ErrRecordNotFound {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-02-20 00:43:08 +00:00
|
|
|
return s, err
|
|
|
|
}
|
2016-02-21 02:53:53 +00:00
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetSMTPByName returns the SMTP, if it exists, specified by the given name and user_id.
|
|
|
|
func GetSMTPByName(n string, uid int64) (SMTP, error) {
|
|
|
|
s := SMTP{}
|
|
|
|
err := db.Where("user_id=? and name=?", uid, n).Find(&s).Error
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-02-20 00:43:08 +00:00
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
err = db.Where("smtp_id=?", s.Id).Find(&s.Headers).Error
|
|
|
|
if err != nil && err != gorm.ErrRecordNotFound {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2016-02-21 02:53:53 +00:00
|
|
|
}
|
|
|
|
return s, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// PostSMTP creates a new SMTP in the database.
|
|
|
|
func PostSMTP(s *SMTP) error {
|
|
|
|
err := s.Validate()
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2016-02-21 02:53:53 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Insert into the DB
|
|
|
|
err = db.Save(s).Error
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2016-02-21 02:53:53 +00:00
|
|
|
}
|
2017-02-20 00:43:08 +00:00
|
|
|
// Save custom headers
|
2017-12-09 21:42:07 +00:00
|
|
|
for i := range s.Headers {
|
2017-02-20 00:43:08 +00:00
|
|
|
s.Headers[i].SMTPId = s.Id
|
|
|
|
err := db.Save(&s.Headers[i]).Error
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-02-20 00:43:08 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2016-02-21 02:53:53 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// PutSMTP edits an existing SMTP in the database.
|
|
|
|
// Per the PUT Method RFC, it presumes all data for a SMTP is provided.
|
|
|
|
func PutSMTP(s *SMTP) error {
|
2016-02-22 04:12:47 +00:00
|
|
|
err := s.Validate()
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2016-02-22 04:12:47 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = db.Where("id=?", s.Id).Save(s).Error
|
2016-02-21 02:53:53 +00:00
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2016-02-21 02:53:53 +00:00
|
|
|
}
|
2017-02-20 00:43:08 +00:00
|
|
|
// Delete all custom headers, and replace with new ones
|
|
|
|
err = db.Where("smtp_id=?", s.Id).Delete(&Header{}).Error
|
|
|
|
if err != nil && err != gorm.ErrRecordNotFound {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-02-20 00:43:08 +00:00
|
|
|
return err
|
|
|
|
}
|
2019-04-23 22:31:30 +00:00
|
|
|
// Save custom headers
|
2017-12-09 21:42:07 +00:00
|
|
|
for i := range s.Headers {
|
2017-02-20 00:43:08 +00:00
|
|
|
s.Headers[i].SMTPId = s.Id
|
|
|
|
err := db.Save(&s.Headers[i]).Error
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-02-20 00:43:08 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2016-02-21 02:53:53 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteSMTP deletes an existing SMTP in the database.
|
|
|
|
// An error is returned if a SMTP with the given user id and SMTP id is not found.
|
|
|
|
func DeleteSMTP(id int64, uid int64) error {
|
2017-02-20 00:43:08 +00:00
|
|
|
// Delete all custom headers
|
2018-12-15 21:42:32 +00:00
|
|
|
err := db.Where("smtp_id=?", id).Delete(&Header{}).Error
|
2017-02-20 00:43:08 +00:00
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-02-20 00:43:08 +00:00
|
|
|
return err
|
|
|
|
}
|
2016-02-21 02:53:53 +00:00
|
|
|
err = db.Where("user_id=?", uid).Delete(SMTP{Id: id}).Error
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2016-02-21 02:53:53 +00:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|