diff --git a/core/blacklist.go b/core/blacklist.go index f2970dd..fa2abd4 100644 --- a/core/blacklist.go +++ b/core/blacklist.go @@ -75,36 +75,39 @@ func NewBlacklist(path string) (*Blacklist, error) { return bl, nil } -func (bl *Blacklist) AddIP(ip string) error { - if bl.IsBlacklisted(ip) { - return nil - } +func (bl *Blacklist) AddIP(ips ...string) error { + for _, ip := range ips { + if bl.IsBlacklisted(ip) { + return nil + } - ipv4 := net.ParseIP(ip) - if ipv4 != nil { - bl.ips[ipv4.String()] = &BlockIP{ipv4: ipv4, mask: nil} - } else { - return fmt.Errorf("blacklist: invalid ip address: %s", ip) - } + ipv4 := net.ParseIP(ip) + if ipv4 != nil { + bl.ips[ipv4.String()] = &BlockIP{ipv4: ipv4, mask: nil} + } else { + return fmt.Errorf("blacklist: invalid ip address: %s", ip) + } - // write to file - f, err := os.OpenFile(bl.configPath, os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - return err - } - defer f.Close() + // write to file + f, err := os.OpenFile(bl.configPath, os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() - _, err = f.WriteString(ipv4.String() + "\n") - if err != nil { - return err + _, err = f.WriteString(ipv4.String() + "\n") + if err != nil { + return err + } + log.Success("successfully blacklisted '%s'", ip) } - return nil } func (bl *Blacklist) IsBlacklisted(ip string) bool { ipv4 := net.ParseIP(ip) if ipv4 == nil { + log.Error("not a valid ip '%s'", ip) return false } @@ -118,3 +121,27 @@ func (bl *Blacklist) IsBlacklisted(ip string) bool { } return false } + +func (bl *Blacklist) IPs() (ips []string, err error) { + f, err := os.Open(bl.configPath) + if err != nil { + return nil, err + } + + defer func() { + if err != nil { + f.Close() + } + }() + + s := bufio.NewScanner(f) + for s.Scan() { + ips = append(ips, s.Text()) + } + + if err := s.Err(); err != nil { + return nil, fmt.Errorf("IPs: error reading blacklist: %w", err) + } + + return +} diff --git a/core/table.go b/core/table.go index 374ab41..499668a 100644 --- a/core/table.go +++ b/core/table.go @@ -144,6 +144,48 @@ func AsTable(columns []string, rows [][]string) string { return table } +//AsSingleColTable creates a single column ascii table. +// col is the header of the table and cells are the individual rows under the header col +func AsSingleColTable(col string, cells []string) string { + dg := color.New(color.FgHiBlack) + + rMaxLens := make([]int, 0) + + for _, cell := range cells { + rlen := viewLen(fmt.Sprintf(" %s ", cell)) + 4 + if rlen < minColLen { + rlen = minColLen + } + rMaxLens = append(rMaxLens, rlen) + } + + //lets find the longest column and set that to be the maxwidthlen + var MaxLen int + for _, len := range rMaxLens { + if len > MaxLen { + MaxLen = len + } + } + + var lineSep string + + lineSep += fmt.Sprintf("+%s", strings.Repeat("-", MaxLen+1)) + lineSep += "+" + + var table string + table += dg.Sprintf("%s\n", lineSep) + table += dg.Sprintf("|") + padded(col, MaxLen, AlignCenter) + table += dg.Sprintf("|\n") + table += dg.Sprintf("%s\n", lineSep) + for _, cell := range cells { + table += dg.Sprintf("|") + padded(cell, MaxLen, AlignCenter) + table += dg.Sprintf("|\n") + } + + table += dg.Sprintf(lineSep) + "\n" + return table +} + func AsRows(keys []string, vals []string) string { clr := color.New(color.FgHiBlack) mLen := maxLen(keys) diff --git a/core/terminal.go b/core/terminal.go index 83e3f5c..de8254b 100644 --- a/core/terminal.go +++ b/core/terminal.go @@ -31,6 +31,15 @@ const ( LAYER_TOP = 1 ) +var ( + lblue = color.New(color.FgHiBlue) + dgray = color.New(color.FgHiBlack) + lgreen = color.New(color.FgHiGreen) + yellow = color.New(color.FgYellow) + lred = color.New(color.FgHiRed) + cyan = color.New(color.FgCyan) +) + type Terminal struct { rl *readline.Instance completer *readline.PrefixCompleter @@ -124,59 +133,59 @@ func (t *Terminal) ProcessCommand(line string) bool { cmd_ok := false switch args[0] { - case "clear": - cmd_ok = true - readline.ClearScreen(color.Output) - case "config": - cmd_ok = true - err := t.handleConfig(args[1:]) - if err != nil { - log.Error("config: %v", err) - } - case "proxy": - cmd_ok = true - err := t.handleProxy(args[1:]) - if err != nil { - log.Error("proxy: %v", err) - } - case "sessions": - cmd_ok = true - err := t.handleSessions(args[1:]) - if err != nil { - log.Error("sessions: %v", err) - } - case "phishlets": - cmd_ok = true - err := t.handlePhishlets(args[1:]) - if err != nil { - log.Error("phishlets: %v", err) - } - case "lures": - cmd_ok = true - err := t.handleLures(args[1:]) - if err != nil { - log.Error("lures: %v", err) - } - case "blacklist": - cmd_ok = true - err := t.handleBlacklist(args[1:]) - if err != nil { - log.Error("blacklist: %v", err) - } - case "help": - cmd_ok = true - if len(args) == 2 { - if err := t.hlp.PrintBrief(args[1]); err != nil { - log.Error("help: %v", err) - } - } else { - t.hlp.Print(0) + case "clear": + cmd_ok = true + readline.ClearScreen(color.Output) + case "config": + cmd_ok = true + err := t.handleConfig(args[1:]) + if err != nil { + log.Error("config: %v", err) + } + case "proxy": + cmd_ok = true + err := t.handleProxy(args[1:]) + if err != nil { + log.Error("proxy: %v", err) + } + case "sessions": + cmd_ok = true + err := t.handleSessions(args[1:]) + if err != nil { + log.Error("sessions: %v", err) + } + case "phishlets": + cmd_ok = true + err := t.handlePhishlets(args[1:]) + if err != nil { + log.Error("phishlets: %v", err) + } + case "lures": + cmd_ok = true + err := t.handleLures(args[1:]) + if err != nil { + log.Error("lures: %v", err) + } + case "blacklist": + cmd_ok = true + err := t.handleBlacklist(args[1:]) + if err != nil { + log.Error("blacklist: %v", err) + } + case "help": + cmd_ok = true + if len(args) == 2 { + if err := t.hlp.PrintBrief(args[1]); err != nil { + log.Error("help: %v", err) } - case "q", "quit", "exit": - return true - default: - log.Error("unknown command: %s", args[0]) - cmd_ok = true + } else { + t.hlp.Print(0) + } + case "q", "quit", "exit": + return true + default: + log.Error("unknown command: %s", args[0]) + cmd_ok = true } if !cmd_ok { log.Error("invalid syntax: %s", line) @@ -244,6 +253,32 @@ func (t *Terminal) handleBlacklist(args []string) error { case "off": t.cfg.SetBlacklistMode(args[0]) return nil + case "show": + ips, err := t.p.bl.IPs() + if err != nil { + log.Error("%v", err) + break + } + if len(ips) > 0 { + log.Printf("\n%s\n", AsSingleColTable("ip", ips)) + } else { + log.Printf("%s", dgray.Sprintf("none")) + } + return nil + } + } else if pn >= 2 { + switch args[0] { + case "add": + err := t.p.bl.AddIP(args[1:]...) + if err != nil { + log.Error("%v", err) + return nil + } + return nil + case "exists": + exists := t.p.bl.IsBlacklisted(args[1]) + log.Success("%v", exists) + return nil } } return fmt.Errorf("invalid syntax: %s", args) @@ -329,12 +364,6 @@ func (t *Terminal) handleProxy(args []string) error { } func (t *Terminal) handleSessions(args []string) error { - lblue := color.New(color.FgHiBlue) - dgray := color.New(color.FgHiBlack) - lgreen := color.New(color.FgHiGreen) - yellow := color.New(color.FgYellow) - lred := color.New(color.FgHiRed) - cyan := color.New(color.FgCyan) pn := len(args) if pn == 0 { @@ -508,13 +537,13 @@ func (t *Terminal) handleSessions(args []string) error { log.Info("exported sessions to csv: %s", outFile.Name()) case "json": type ExportedSession struct { - Id string `json:"id"` - Phishlet string `json:"phishlet"` - Username string `json:"username"` - Password string `json:"password"` - Tokens string `json:"tokens_base64_encoded"` + Id string `json:"id"` + Phishlet string `json:"phishlet"` + Username string `json:"username"` + Password string `json:"password"` + Tokens string `json:"tokens_base64_encoded"` RemoteAddr string `json:"remote_ip"` - Time string `json:"time"` + Time string `json:"time"` } var exported []*ExportedSession for _, s := range sessions { @@ -524,13 +553,13 @@ func (t *Terminal) handleSessions(args []string) error { break } es := &ExportedSession{ - Id: strconv.Itoa(s.Id), - Phishlet: s.Phishlet, - Username: s.Username, - Password: s.Password, - Tokens: base64.StdEncoding.EncodeToString([]byte(t.tokensToJSON(pl, s.Tokens))), + Id: strconv.Itoa(s.Id), + Phishlet: s.Phishlet, + Username: s.Username, + Password: s.Password, + Tokens: base64.StdEncoding.EncodeToString([]byte(t.tokensToJSON(pl, s.Tokens))), RemoteAddr: s.RemoteAddr, - Time: time.Unix(s.UpdateTime, 0).Format("2006-01-02 15:04"), + Time: time.Unix(s.UpdateTime, 0).Format("2006-01-02 15:04"), } exported = append(exported, es) } @@ -1116,6 +1145,9 @@ func (t *Terminal) createHelp() { h.AddSubCommand("blacklist", []string{"all"}, "all", "block and blacklist ip addresses for every single request (even authorized ones!)") h.AddSubCommand("blacklist", []string{"unauth"}, "unauth", "block and blacklist ip addresses only for unauthorized requests") h.AddSubCommand("blacklist", []string{"off"}, "off", "never add any ip addresses to blacklist") + h.AddSubCommand("blacklist", []string{"show"}, "show", "list all blacklisted ip addresses") + h.AddSubCommand("blacklist", []string{"add"}, "add ...", "add one or more ips to blacklist") + h.AddSubCommand("blacklist", []string{"exists"}, "exists ", "verifies if the given ip is already blacklisted") h.AddCommand("clear", "general", "clears the screen", "Clears the screen.", LAYER_TOP, readline.PcItem("clear")) diff --git a/main_test.go b/main_test.go index 32ba31f..92d7c39 100644 --- a/main_test.go +++ b/main_test.go @@ -101,6 +101,20 @@ func TestStart(t *testing.T) { log.Println("Finished configuration, setting up HTTP") time.Sleep(1 * time.Second) + //Test Blacklist mode + log.Println("Testing blacklist mode") + terminal.ProcessCommand("blacklist") + test.assertLogContains("blacklist mode set to: off", "Default blacklist mode") //default mode + terminal.ProcessCommand("blacklist show") + test.assertLogContains("", "Can list blacklisted ips") //we expect none at first since blacklist mode is off + terminal.ProcessCommand("blacklist add 127.0.0.2") + terminal.ProcessCommand("blacklist show") + test.assertLogContains("127.0.0.2", "Can add ip to blacklist") + terminal.ProcessCommand("blacklist exists 127.0.0.2") + test.assertLogContains("true", "Can verify ip exists in blacklist") + terminal.ProcessCommand("blacklist exists 127.0.0.1") + test.assertLogContains("false", "Can verify ip does not exist in blacklist") + // Test HTTP requests log.Println("Testing interaction") _, url, _, _ := test.HttpGet("https://www.localhost") @@ -127,8 +141,8 @@ func TestStart(t *testing.T) { if redditPassword == "" { log.Println("[SKIP]", "Valid login tests skipped due to missing environment variable") return - } - + } + _, _, body, _ = test.HttpPost("https://www.localhost/login", baseData+redditPassword) test.assertContains(body, "https://www.localhost", "Valid login is accepted") test.assertLogContains("all authorization tokens intercepted", "Valid login is detected as correct") @@ -143,10 +157,10 @@ func TestStart(t *testing.T) { test.assertLogContains("captured", "Session token captured") test.assertLogContains(`","name":"reddit_session","httpOnly":true`, "Session cookie displayed") test.Clear() - - exportPath := path+"/export.json" + + exportPath := path + "/export.json" os.RemoveAll(exportPath) - terminal.ProcessCommand("sessions export json "+strings.ReplaceAll(exportPath, `\`, `\\`)) + terminal.ProcessCommand("sessions export json " + strings.ReplaceAll(exportPath, `\`, `\\`)) test.assertLogContains("exported sessions to json", "Can export sessions to file") time.Sleep(1 * time.Second) readDump, err := ioutil.ReadFile(exportPath)