2017-06-09 04:41:38 +00:00
|
|
|
package controllers
|
|
|
|
|
|
|
|
import (
|
2018-12-15 21:42:32 +00:00
|
|
|
"compress/gzip"
|
|
|
|
"context"
|
2017-06-09 04:41:38 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
2018-06-10 01:58:05 +00:00
|
|
|
"time"
|
2017-06-09 04:41:38 +00:00
|
|
|
|
2018-12-15 21:42:32 +00:00
|
|
|
"github.com/NYTimes/gziphandler"
|
2018-06-10 01:58:05 +00:00
|
|
|
"github.com/gophish/gophish/config"
|
2017-06-09 04:41:38 +00:00
|
|
|
ctx "github.com/gophish/gophish/context"
|
2019-03-27 03:17:20 +00:00
|
|
|
"github.com/gophish/gophish/controllers/api"
|
2018-05-04 00:07:41 +00:00
|
|
|
log "github.com/gophish/gophish/logger"
|
2017-06-09 04:41:38 +00:00
|
|
|
"github.com/gophish/gophish/models"
|
2018-12-15 21:42:32 +00:00
|
|
|
"github.com/gophish/gophish/util"
|
|
|
|
"github.com/gorilla/handlers"
|
2017-06-09 04:41:38 +00:00
|
|
|
"github.com/gorilla/mux"
|
2018-12-15 21:42:32 +00:00
|
|
|
"github.com/jordan-wright/unindexed"
|
2017-06-09 04:41:38 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// ErrInvalidRequest is thrown when a request with an invalid structure is
|
|
|
|
// received
|
|
|
|
var ErrInvalidRequest = errors.New("Invalid request")
|
|
|
|
|
|
|
|
// ErrCampaignComplete is thrown when an event is received for a campaign that
|
|
|
|
// has already been marked as complete.
|
|
|
|
var ErrCampaignComplete = errors.New("Event received on completed campaign")
|
|
|
|
|
2018-06-10 01:58:05 +00:00
|
|
|
// TransparencyResponse is the JSON response provided when a third-party
|
|
|
|
// makes a request to the transparency handler.
|
|
|
|
type TransparencyResponse struct {
|
|
|
|
Server string `json:"server"`
|
|
|
|
ContactAddress string `json:"contact_address"`
|
|
|
|
SendDate time.Time `json:"send_date"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// TransparencySuffix (when appended to a valid result ID), will cause Gophish
|
|
|
|
// to return a transparency response.
|
|
|
|
const TransparencySuffix = "+"
|
|
|
|
|
2018-12-15 21:42:32 +00:00
|
|
|
// PhishingServerOption is a functional option that is used to configure the
|
|
|
|
// the phishing server
|
|
|
|
type PhishingServerOption func(*PhishingServer)
|
|
|
|
|
|
|
|
// PhishingServer is an HTTP server that implements the campaign event
|
|
|
|
// handlers, such as email open tracking, click tracking, and more.
|
|
|
|
type PhishingServer struct {
|
|
|
|
server *http.Server
|
|
|
|
config config.PhishServer
|
|
|
|
contactAddress string
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewPhishingServer returns a new instance of the phishing server with
|
|
|
|
// provided options applied.
|
|
|
|
func NewPhishingServer(config config.PhishServer, options ...PhishingServerOption) *PhishingServer {
|
|
|
|
defaultServer := &http.Server{
|
|
|
|
ReadTimeout: 10 * time.Second,
|
|
|
|
WriteTimeout: 10 * time.Second,
|
|
|
|
Addr: config.ListenURL,
|
|
|
|
}
|
|
|
|
ps := &PhishingServer{
|
|
|
|
server: defaultServer,
|
|
|
|
config: config,
|
|
|
|
}
|
|
|
|
for _, opt := range options {
|
|
|
|
opt(ps)
|
|
|
|
}
|
|
|
|
ps.registerRoutes()
|
|
|
|
return ps
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithContactAddress sets the contact address used by the transparency
|
|
|
|
// handlers
|
|
|
|
func WithContactAddress(addr string) PhishingServerOption {
|
|
|
|
return func(ps *PhishingServer) {
|
|
|
|
ps.contactAddress = addr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start launches the phishing server, listening on the configured address.
|
2019-10-29 02:38:59 +00:00
|
|
|
func (ps *PhishingServer) Start() {
|
2018-12-15 21:42:32 +00:00
|
|
|
if ps.config.UseTLS {
|
2019-12-12 01:52:41 +00:00
|
|
|
// Only support TLS 1.2 and above - ref #1691, #1689
|
Updated the TLS configuration.
This commit removes support for the TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA and TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA ciphers. It also removes support for CurveP384. This is to match up with recommendations given by Cloudflare [0], Mozilla (the "Intermediate" compatibility) [1], and referencing the default ciphers in Caddy [2].
[0] https://blog.cloudflare.com/exposing-go-on-the-internet/
[1] https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28recommended.29
[2] https://github.com/caddyserver/caddy/blob/008415f206dec7c72c3b0abe36d5a40213abb7a2/caddytls/config.go#L492
Here is the diff of running testssl against the old and new configurations:
```
git diff --no-index -- old.txt new.txt
diff --git a/old.txt b/new.txt
index fc624a1..53d0c97 100644
--- a/old.txt
+++ b/new.txt
@@ -13,11 +13,11 @@ docker run --rm -ti -p 3333:3333 drwetter/testssl.sh https://host.docker.interna
###########################################################
Using "OpenSSL 1.0.2-chacha (1.0.2k-dev)" [~183 ciphers]
- on 4831dc55e53f:$PWD/bin/openssl.Linux.x86_64
+ on 41ae723da66a:$PWD/bin/openssl.Linux.x86_64
(built: "Jan 18 17:12:17 2019", platform: "linux-x86_64")
- Start 2020-03-28 02:54:41 -->> 192.168.65.2:3333 (host.docker.internal) <<--
+ Start 2020-03-28 03:15:21 -->> 192.168.65.2:3333 (host.docker.internal) <<--
rDNS (192.168.65.2): --
Service detected: HTTP
@@ -41,15 +41,14 @@ docker run --rm -ti -p 3333:3333 drwetter/testssl.sh https://host.docker.interna
Export ciphers (w/o ADH+NULL) not offered (OK)
LOW: 64 Bit + DES, RC[2,4] (w/o export) not offered (OK)
Triple DES Ciphers / IDEA not offered (OK)
- Obsolete: SEED + 128+256 Bit CBC cipher offered
+ Obsolete: SEED + 128+256 Bit CBC cipher not offered
Strong encryption (AEAD ciphers) offered (OK)
Testing robust (perfect) forward secrecy, (P)FS -- omitting Null Authentication/Encryption, 3DES, RC4
- PFS is offered (OK) TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES256-SHA ECDHE-ECDSA-CHACHA20-POLY1305 TLS_AES_128_GCM_SHA256
- ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES128-SHA
- Elliptic curves offered: prime256v1 secp384r1 secp521r1 X25519
+ PFS is offered (OK) TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305 TLS_AES_128_GCM_SHA256 ECDHE-ECDSA-AES128-GCM-SHA256
+ Elliptic curves offered: prime256v1 X25519
Testing server preferences
@@ -58,7 +57,7 @@ docker run --rm -ti -p 3333:3333 drwetter/testssl.sh https://host.docker.interna
Negotiated protocol TLSv1.3
Negotiated cipher TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
Cipher order
- TLSv1.2: ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES128-SHA ECDHE-ECDSA-AES256-SHA
+ TLSv1.2: ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES128-GCM-SHA256
TLSv1.3: TLS_AES_128_GCM_SHA256 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_256_GCM_SHA384
@@ -125,7 +124,7 @@ docker run --rm -ti -p 3333:3333 drwetter/testssl.sh https://host.docker.interna
no RSA certificate, thus certificate can't be used with SSLv2 elsewhere
LOGJAM (CVE-2015-4000), experimental not vulnerable (OK): no DH EXPORT ciphers, no DH key detected with <= TLS 1.2
BEAST (CVE-2011-3389) no SSL3 or TLS1 (OK)
- LUCKY13 (CVE-2013-0169), experimental potentially VULNERABLE, uses cipher block chaining (CBC) ciphers with TLS. Check patches
+ LUCKY13 (CVE-2013-0169), experimental not vulnerable (OK)
RC4 (CVE-2013-2566, CVE-2015-2808) no RC4 ciphers detected (OK)
@@ -136,50 +135,48 @@ Hexcode Cipher Suite Name (OpenSSL) KeyExch. Encryption Bits Ciphe
x1302 TLS_AES_256_GCM_SHA384 ECDH 253 AESGCM 256 TLS_AES_256_GCM_SHA384
x1303 TLS_CHACHA20_POLY1305_SHA256 ECDH 253 ChaCha20 256 TLS_CHACHA20_POLY1305_SHA256
xc02c ECDHE-ECDSA-AES256-GCM-SHA384 ECDH 256 AESGCM 256 TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- xc00a ECDHE-ECDSA-AES256-SHA ECDH 256 AES 256 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
xcca9 ECDHE-ECDSA-CHACHA20-POLY1305 ECDH 253 ChaCha20 256 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
x1301 TLS_AES_128_GCM_SHA256 ECDH 253 AESGCM 128 TLS_AES_128_GCM_SHA256
xc02b ECDHE-ECDSA-AES128-GCM-SHA256 ECDH 256 AESGCM 128 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- xc009 ECDHE-ECDSA-AES128-SHA ECDH 256 AES 128 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
- LUCKY13 (CVE-2013-0169), experimental potentially VULNERABLE, uses cipher block chaining (CBC) ciphers with TLS. Check patches
+ LUCKY13 (CVE-2013-0169), experimental not vulnerable (OK)
RC4 (CVE-2013-2566, CVE-2015-2808) no RC4 ciphers detected (OK)
@@ -136,50 +135,48 @@ Hexcode Cipher Suite Name (OpenSSL) KeyExch. Encryption Bits Ciphe
x1302 TLS_AES_256_GCM_SHA384 ECDH 253 AESGCM 256 TLS_AES_256_GCM_SHA384
x1303 TLS_CHACHA20_POLY1305_SHA256 ECDH 253 ChaCha20 256 TLS_CHACHA20_POLY1305_SHA256
xc02c ECDHE-ECDSA-AES256-GCM-SHA384 ECDH 256 AESGCM 256 TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- xc00a ECDHE-ECDSA-AES256-SHA ECDH 256 AES 256 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
xcca9 ECDHE-ECDSA-CHACHA20-POLY1305 ECDH 253 ChaCha20 256 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
x1301 TLS_AES_128_GCM_SHA256 ECDH 253 AESGCM 128 TLS_AES_128_GCM_SHA256
xc02b ECDHE-ECDSA-AES128-GCM-SHA256 ECDH 256 AESGCM 128 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- xc009 ECDHE-ECDSA-AES128-SHA ECDH 256 AES 128 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
Running client simulations (HTTP) via sockets
- Android 4.4.2 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
+ Android 4.4.2 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
Android 5.0.0 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
Android 6.0 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
- Android 7.0 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 253 bit ECDH (X25519)
- Android 8.1 (native) TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 253 bit ECDH (X25519)
+ Android 7.0 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 253 bit ECDH (X25519)
+ Android 8.1 (native) TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 253 bit ECDH (X25519)
Android 9.0 (native) TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
- Chrome 65 Win 7 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 253 bit ECDH (X25519)
+ Chrome 65 Win 7 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 253 bit ECDH (X25519)
Chrome 74 (Win 10) TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
- Firefox 62 Win 7 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 253 bit ECDH (X25519)
+ Firefox 62 Win 7 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 253 bit ECDH (X25519)
Firefox 66 (Win 8.1/10) TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
IE 6 XP No connection
IE 8 Win 7 No connection
IE 8 XP No connection
- IE 11 Win 7 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
- IE 11 Win 8.1 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
- IE 11 Win Phone 8.1 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
- IE 11 Win 10 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
- Edge 15 Win 10 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 253 bit ECDH (X25519)
- Edge 17 (Win 10) TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 253 bit ECDH (X25519)
+ IE 11 Win 7 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
+ IE 11 Win 8.1 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
+ IE 11 Win Phone 8.1 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
+ IE 11 Win 10 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
+ Edge 15 Win 10 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 253 bit ECDH (X25519)
+ Edge 17 (Win 10) TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 253 bit ECDH (X25519)
Opera 60 (Win 10) TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
- Safari 9 iOS 9 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
- Safari 9 OS X 10.11 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
- Safari 10 OS X 10.12 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
+ Safari 9 iOS 9 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
+ Safari 9 OS X 10.11 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
+ Safari 10 OS X 10.12 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
Safari 12.1 (iOS 12.2) TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
Safari 13.0 (macOS 10.14.6) TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
- Apple ATS 9 iOS 9 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
+ Apple ATS 9 iOS 9 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
Java 6u45 No connection
Java 7u25 No connection
- Java 8u161 TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
+ Java 8u161 TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
Java 11.0.2 (OpenJDK) TLSv1.3 TLS_AES_128_GCM_SHA256, 256 bit ECDH (P-256)
Java 12.0.1 (OpenJDK) TLSv1.3 TLS_AES_128_GCM_SHA256, 256 bit ECDH (P-256)
- OpenSSL 1.0.1l TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
- OpenSSL 1.0.2e TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 256 bit ECDH (P-256)
- OpenSSL 1.1.0j (Debian) TLSv1.2 ECDHE-ECDSA-AES128-GCM-SHA256, 253 bit ECDH (X25519)
+ OpenSSL 1.0.1l TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
+ OpenSSL 1.0.2e TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 256 bit ECDH (P-256)
+ OpenSSL 1.1.0j (Debian) TLSv1.2 ECDHE-ECDSA-AES256-GCM-SHA384, 253 bit ECDH (X25519)
OpenSSL 1.1.1b (Debian) TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
Thunderbird (60.6) TLSv1.3 TLS_AES_128_GCM_SHA256, 253 bit ECDH (X25519)
- Done 2020-03-28 02:57:48 [ 189s] -->> 192.168.65.2:3333 (host.docker.internal) <<--
+ Done 2020-03-28 03:17:25 [ 128s] -->> 192.168.65.2:3333 (host.docker.internal) <<--
```
Fixes #1698
2020-03-28 03:25:18 +00:00
|
|
|
ps.server.TLSConfig = defaultTLSConfig
|
2018-12-15 21:42:32 +00:00
|
|
|
err := util.CheckAndCreateSSL(ps.config.CertPath, ps.config.KeyPath)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
log.Infof("Starting phishing server at https://%s", ps.config.ListenURL)
|
2019-10-29 02:38:59 +00:00
|
|
|
log.Fatal(ps.server.ListenAndServeTLS(ps.config.CertPath, ps.config.KeyPath))
|
2018-12-15 21:42:32 +00:00
|
|
|
}
|
|
|
|
// If TLS isn't configured, just listen on HTTP
|
|
|
|
log.Infof("Starting phishing server at http://%s", ps.config.ListenURL)
|
2019-10-29 02:38:59 +00:00
|
|
|
log.Fatal(ps.server.ListenAndServe())
|
2018-12-15 21:42:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Shutdown attempts to gracefully shutdown the server.
|
|
|
|
func (ps *PhishingServer) Shutdown() error {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
|
|
|
defer cancel()
|
|
|
|
return ps.server.Shutdown(ctx)
|
|
|
|
}
|
|
|
|
|
2017-06-09 04:41:38 +00:00
|
|
|
// CreatePhishingRouter creates the router that handles phishing connections.
|
2018-12-15 21:42:32 +00:00
|
|
|
func (ps *PhishingServer) registerRoutes() {
|
2017-06-09 04:41:38 +00:00
|
|
|
router := mux.NewRouter()
|
2018-12-15 21:42:32 +00:00
|
|
|
fileServer := http.FileServer(unindexed.Dir("./static/endpoint/"))
|
2018-05-24 04:03:48 +00:00
|
|
|
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fileServer))
|
2018-12-15 21:42:32 +00:00
|
|
|
router.HandleFunc("/track", ps.TrackHandler)
|
|
|
|
router.HandleFunc("/robots.txt", ps.RobotsHandler)
|
2020-08-07 19:51:25 +00:00
|
|
|
router.HandleFunc("/arbevent", ps.ArbitraryEventHandler)
|
2018-12-15 21:42:32 +00:00
|
|
|
router.HandleFunc("/{path:.*}/track", ps.TrackHandler)
|
|
|
|
router.HandleFunc("/{path:.*}/report", ps.ReportHandler)
|
|
|
|
router.HandleFunc("/report", ps.ReportHandler)
|
|
|
|
router.HandleFunc("/{path:.*}", ps.PhishHandler)
|
|
|
|
|
|
|
|
// Setup GZIP compression
|
|
|
|
gzipWrapper, _ := gziphandler.NewGzipLevelHandler(gzip.BestCompression)
|
|
|
|
phishHandler := gzipWrapper(router)
|
|
|
|
|
|
|
|
// Setup logging
|
|
|
|
phishHandler = handlers.CombinedLoggingHandler(log.Writer(), phishHandler)
|
2019-02-20 03:40:26 +00:00
|
|
|
ps.server.Handler = phishHandler
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
|
2020-08-07 19:51:25 +00:00
|
|
|
// ArbitraryEventHandler deals with arbitrary events - for example opening Word documents, secondary links, etc
|
|
|
|
func (ps *PhishingServer) ArbitraryEventHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
|
|
r, err := setupContext(r)
|
|
|
|
if err != nil {
|
|
|
|
// Log the error if it wasn't something we can safely ignore
|
|
|
|
if err != ErrInvalidRequest && err != ErrCampaignComplete {
|
|
|
|
log.Error(err)
|
|
|
|
}
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
rs := ctx.Get(r, "result").(models.Result)
|
|
|
|
d := ctx.Get(r, "details").(models.EventDetails)
|
|
|
|
|
|
|
|
err = rs.HandleArbitraryEvent(d)
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err)
|
|
|
|
w.Write([]byte(err.Error()))
|
|
|
|
} else {
|
|
|
|
|
|
|
|
w.Write([]byte("Event received"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-15 21:42:32 +00:00
|
|
|
// TrackHandler tracks emails as they are opened, updating the status for the given Result
|
|
|
|
func (ps *PhishingServer) TrackHandler(w http.ResponseWriter, r *http.Request) {
|
2018-12-16 03:38:51 +00:00
|
|
|
r, err := setupContext(r)
|
2017-06-09 04:41:38 +00:00
|
|
|
if err != nil {
|
|
|
|
// Log the error if it wasn't something we can safely ignore
|
|
|
|
if err != ErrInvalidRequest && err != ErrCampaignComplete {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
2018-06-09 02:20:52 +00:00
|
|
|
// Check for a preview
|
|
|
|
if _, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
|
|
|
|
http.ServeFile(w, r, "static/images/pixel.png")
|
|
|
|
return
|
|
|
|
}
|
2017-06-09 04:41:38 +00:00
|
|
|
rs := ctx.Get(r, "result").(models.Result)
|
2018-06-10 01:58:05 +00:00
|
|
|
rid := ctx.Get(r, "rid").(string)
|
2018-05-27 02:26:34 +00:00
|
|
|
d := ctx.Get(r, "details").(models.EventDetails)
|
2018-06-10 01:58:05 +00:00
|
|
|
|
|
|
|
// Check for a transparency request
|
|
|
|
if strings.HasSuffix(rid, TransparencySuffix) {
|
2018-12-15 21:42:32 +00:00
|
|
|
ps.TransparencyHandler(w, r)
|
2018-06-10 01:58:05 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-05-27 02:26:34 +00:00
|
|
|
err = rs.HandleEmailOpened(d)
|
2017-06-09 04:41:38 +00:00
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
http.ServeFile(w, r, "static/images/pixel.png")
|
|
|
|
}
|
|
|
|
|
2018-12-15 21:42:32 +00:00
|
|
|
// ReportHandler tracks emails as they are reported, updating the status for the given Result
|
|
|
|
func (ps *PhishingServer) ReportHandler(w http.ResponseWriter, r *http.Request) {
|
2018-12-16 03:38:51 +00:00
|
|
|
r, err := setupContext(r)
|
2019-08-04 01:55:25 +00:00
|
|
|
w.Header().Set("Access-Control-Allow-Origin", "*") // To allow Chrome extensions (or other pages) to report a campaign without violating CORS
|
2018-03-19 03:03:00 +00:00
|
|
|
if err != nil {
|
|
|
|
// Log the error if it wasn't something we can safely ignore
|
|
|
|
if err != ErrInvalidRequest && err != ErrCampaignComplete {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2018-03-19 03:03:00 +00:00
|
|
|
}
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
2018-06-09 02:20:52 +00:00
|
|
|
// Check for a preview
|
|
|
|
if _, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
return
|
|
|
|
}
|
2018-03-19 03:03:00 +00:00
|
|
|
rs := ctx.Get(r, "result").(models.Result)
|
2018-06-10 01:58:05 +00:00
|
|
|
rid := ctx.Get(r, "rid").(string)
|
2018-05-27 02:26:34 +00:00
|
|
|
d := ctx.Get(r, "details").(models.EventDetails)
|
2018-03-19 03:03:00 +00:00
|
|
|
|
2018-06-10 01:58:05 +00:00
|
|
|
// Check for a transparency request
|
|
|
|
if strings.HasSuffix(rid, TransparencySuffix) {
|
2018-12-15 21:42:32 +00:00
|
|
|
ps.TransparencyHandler(w, r)
|
2018-06-10 01:58:05 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-05-27 02:26:34 +00:00
|
|
|
err = rs.HandleEmailReport(d)
|
2018-03-19 03:03:00 +00:00
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2018-03-19 03:03:00 +00:00
|
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
}
|
|
|
|
|
2017-06-09 04:41:38 +00:00
|
|
|
// PhishHandler handles incoming client connections and registers the associated actions performed
|
|
|
|
// (such as clicked link, etc.)
|
2018-12-15 21:42:32 +00:00
|
|
|
func (ps *PhishingServer) PhishHandler(w http.ResponseWriter, r *http.Request) {
|
2018-12-16 03:38:51 +00:00
|
|
|
r, err := setupContext(r)
|
2017-06-09 04:41:38 +00:00
|
|
|
if err != nil {
|
|
|
|
// Log the error if it wasn't something we can safely ignore
|
|
|
|
if err != ErrInvalidRequest && err != ErrCampaignComplete {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
2019-08-04 01:55:25 +00:00
|
|
|
w.Header().Set("X-Server", config.ServerName) // Useful for checking if this is a GoPhish server (e.g. for campaign reporting plugins)
|
2018-06-09 02:20:52 +00:00
|
|
|
var ptx models.PhishingTemplateContext
|
|
|
|
// Check for a preview
|
|
|
|
if preview, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
|
|
|
|
ptx, err = models.NewPhishingTemplateContext(&preview, preview.BaseRecipient, preview.RId)
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err)
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p, err := models.GetPage(preview.PageId, preview.UserId)
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err)
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
renderPhishResponse(w, r, ptx, p)
|
|
|
|
return
|
|
|
|
}
|
2017-06-09 04:41:38 +00:00
|
|
|
rs := ctx.Get(r, "result").(models.Result)
|
2018-06-10 01:58:05 +00:00
|
|
|
rid := ctx.Get(r, "rid").(string)
|
2017-06-09 04:41:38 +00:00
|
|
|
c := ctx.Get(r, "campaign").(models.Campaign)
|
2018-05-27 02:26:34 +00:00
|
|
|
d := ctx.Get(r, "details").(models.EventDetails)
|
2018-06-10 01:58:05 +00:00
|
|
|
|
|
|
|
// Check for a transparency request
|
|
|
|
if strings.HasSuffix(rid, TransparencySuffix) {
|
2018-12-15 21:42:32 +00:00
|
|
|
ps.TransparencyHandler(w, r)
|
2018-06-10 01:58:05 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-06-09 04:41:38 +00:00
|
|
|
p, err := models.GetPage(c.PageId, c.UserId)
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2018-03-23 02:29:07 +00:00
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
switch {
|
|
|
|
case r.Method == "GET":
|
2018-05-27 02:26:34 +00:00
|
|
|
err = rs.HandleClickedLink(d)
|
2017-06-09 04:41:38 +00:00
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
case r.Method == "POST":
|
2018-05-27 02:26:34 +00:00
|
|
|
err = rs.HandleFormSubmit(d)
|
2017-06-09 04:41:38 +00:00
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
}
|
2018-06-09 02:20:52 +00:00
|
|
|
ptx, err = models.NewPhishingTemplateContext(&c, rs.BaseRecipient, rs.RId)
|
2017-06-09 04:41:38 +00:00
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
http.NotFound(w, r)
|
|
|
|
}
|
2018-06-09 02:20:52 +00:00
|
|
|
renderPhishResponse(w, r, ptx, p)
|
|
|
|
}
|
2018-02-23 05:02:27 +00:00
|
|
|
|
2018-06-09 02:20:52 +00:00
|
|
|
// renderPhishResponse handles rendering the correct response to the phishing
|
|
|
|
// 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) {
|
|
|
|
// 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 != "" {
|
2018-10-15 21:42:05 +00:00
|
|
|
redirectURL, err := models.ExecuteTemplate(p.RedirectURL, ptx)
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err)
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
http.Redirect(w, r, redirectURL, http.StatusFound)
|
2018-06-09 02:20:52 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Otherwise, we just need to write out the templated HTML
|
|
|
|
html, err := models.ExecuteTemplate(p.HTML, ptx)
|
2017-06-09 04:41:38 +00:00
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
http.NotFound(w, r)
|
2018-03-23 02:29:07 +00:00
|
|
|
return
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
2018-06-09 02:20:52 +00:00
|
|
|
w.Write([]byte(html))
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// RobotsHandler prevents search engines, etc. from indexing phishing materials
|
2018-12-15 21:42:32 +00:00
|
|
|
func (ps *PhishingServer) RobotsHandler(w http.ResponseWriter, r *http.Request) {
|
2017-06-09 04:41:38 +00:00
|
|
|
fmt.Fprintln(w, "User-agent: *\nDisallow: /")
|
|
|
|
}
|
|
|
|
|
2018-06-10 01:58:05 +00:00
|
|
|
// TransparencyHandler returns a TransparencyResponse for the provided result
|
|
|
|
// and campaign.
|
2018-12-15 21:42:32 +00:00
|
|
|
func (ps *PhishingServer) TransparencyHandler(w http.ResponseWriter, r *http.Request) {
|
2018-06-10 01:58:05 +00:00
|
|
|
rs := ctx.Get(r, "result").(models.Result)
|
|
|
|
tr := &TransparencyResponse{
|
2018-06-19 02:37:59 +00:00
|
|
|
Server: config.ServerName,
|
2018-06-10 01:58:05 +00:00
|
|
|
SendDate: rs.SendDate,
|
2018-12-15 21:42:32 +00:00
|
|
|
ContactAddress: ps.contactAddress,
|
2018-06-10 01:58:05 +00:00
|
|
|
}
|
2019-03-27 03:17:20 +00:00
|
|
|
api.JSONResponse(w, tr, http.StatusOK)
|
2018-06-10 01:58:05 +00:00
|
|
|
}
|
|
|
|
|
2018-12-15 21:42:32 +00:00
|
|
|
// setupContext handles some of the administrative work around receiving a new
|
|
|
|
// request, such as checking the result ID, the campaign, etc.
|
2018-12-16 03:38:51 +00:00
|
|
|
func setupContext(r *http.Request) (*http.Request, error) {
|
2017-06-09 04:41:38 +00:00
|
|
|
err := r.ParseForm()
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2018-12-16 03:38:51 +00:00
|
|
|
return r, err
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
2018-06-10 01:58:05 +00:00
|
|
|
rid := r.Form.Get(models.RecipientParameter)
|
|
|
|
if rid == "" {
|
2018-12-16 03:38:51 +00:00
|
|
|
return r, ErrInvalidRequest
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
2018-06-10 01:58:05 +00:00
|
|
|
// Since we want to support the common case of adding a "+" to indicate a
|
|
|
|
// transparency request, we need to take care to handle the case where the
|
|
|
|
// request ends with a space, since a "+" is technically reserved for use
|
|
|
|
// as a URL encoding of a space.
|
|
|
|
if strings.HasSuffix(rid, " ") {
|
|
|
|
// We'll trim off the space
|
|
|
|
rid = strings.TrimRight(rid, " ")
|
|
|
|
// Then we'll add the transparency suffix
|
|
|
|
rid = fmt.Sprintf("%s%s", rid, TransparencySuffix)
|
|
|
|
}
|
|
|
|
// Finally, if this is a transparency request, we'll need to verify that
|
|
|
|
// a valid rid has been provided, so we'll look up the result with a
|
|
|
|
// trimmed parameter.
|
|
|
|
id := strings.TrimSuffix(rid, TransparencySuffix)
|
2018-06-09 02:20:52 +00:00
|
|
|
// Check to see if this is a preview or a real result
|
|
|
|
if strings.HasPrefix(id, models.PreviewPrefix) {
|
|
|
|
rs, err := models.GetEmailRequestByResultId(id)
|
|
|
|
if err != nil {
|
2018-12-16 03:38:51 +00:00
|
|
|
return r, err
|
2018-06-09 02:20:52 +00:00
|
|
|
}
|
|
|
|
r = ctx.Set(r, "result", rs)
|
2018-12-16 03:38:51 +00:00
|
|
|
return r, nil
|
2018-06-09 02:20:52 +00:00
|
|
|
}
|
2017-06-09 04:41:38 +00:00
|
|
|
rs, err := models.GetResult(id)
|
|
|
|
if err != nil {
|
2018-12-16 03:38:51 +00:00
|
|
|
return r, err
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
c, err := models.GetCampaign(rs.CampaignId, rs.UserId)
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2018-12-16 03:38:51 +00:00
|
|
|
return r, err
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
// Don't process events for completed campaigns
|
2018-12-16 03:38:51 +00:00
|
|
|
if c.Status == models.CampaignComplete {
|
|
|
|
return r, ErrCampaignComplete
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2018-12-16 03:38:51 +00:00
|
|
|
return r, err
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
|
|
|
// Respect X-Forwarded headers
|
|
|
|
if fips := r.Header.Get("X-Forwarded-For"); fips != "" {
|
|
|
|
ip = strings.Split(fips, ", ")[0]
|
|
|
|
}
|
|
|
|
// Handle post processing such as GeoIP
|
|
|
|
err = rs.UpdateGeo(ip)
|
|
|
|
if err != nil {
|
2018-05-04 00:07:41 +00:00
|
|
|
log.Error(err)
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|
2018-05-27 02:26:34 +00:00
|
|
|
d := models.EventDetails{
|
2017-06-09 04:41:38 +00:00
|
|
|
Payload: r.Form,
|
|
|
|
Browser: make(map[string]string),
|
|
|
|
}
|
|
|
|
d.Browser["address"] = ip
|
|
|
|
d.Browser["user-agent"] = r.Header.Get("User-Agent")
|
|
|
|
|
2018-06-10 01:58:05 +00:00
|
|
|
r = ctx.Set(r, "rid", rid)
|
2017-06-09 04:41:38 +00:00
|
|
|
r = ctx.Set(r, "result", rs)
|
|
|
|
r = ctx.Set(r, "campaign", c)
|
2018-05-27 02:26:34 +00:00
|
|
|
r = ctx.Set(r, "details", d)
|
2018-12-16 03:38:51 +00:00
|
|
|
return r, nil
|
2017-06-09 04:41:38 +00:00
|
|
|
}
|