/
ssh.go
225 lines (196 loc) · 5.49 KB
/
ssh.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
package ssh
import (
"fmt"
"log"
"strconv"
"strings"
"sync"
"time"
"github.com/c-f/hygo/model"
"golang.org/x/crypto/ssh"
)
var (
Name = "ssh"
)
// SSH defines configs for a specific target, which can be checked with credentials
type SSH struct {
//stuff
Host string
Port int
// Options
StopIfSuccess bool
StopIfNetErr bool
LogFailedAttempts bool
// timeout
timeout time.Duration
sleep time.Duration
// internals for threading
queue chan model.Credential
active model.AtomicBool
wg sync.WaitGroup
}
// New creates a new SSH Bruter
func New(host string, port int, sleep time.Duration, timeout time.Duration, logAttempts bool) *SSH {
bruter := &SSH{
Host: host,
Port: port,
// Time Stuff
timeout: timeout,
sleep: sleep,
StopIfSuccess: true, // default
StopIfNetErr: true, // default
LogFailedAttempts: logAttempts,
queue: make(chan model.Credential),
}
return bruter
}
// GoStart defines the amout of handlers per target and sets the out and err channels
func (bruter *SSH) GoStart(threads int, outChan chan model.Result, errChan chan model.Err) {
bruter.active.Set(true)
for i := 0; i < threads; i++ {
bruter.wg.Add(1)
go bruter.handle(outChan, errChan)
}
}
// Add uses a credential pair for the connection blocking !
func (bruter *SSH) Add(c model.Credential) {
if bruter.active.Get() {
bruter.queue <- c
}
return
}
// Close signals the target to cease all activity.
func (bruter *SSH) Close() {
bruter.active.Set(false)
close(bruter.queue)
bruter.wg.Wait()
}
// handle handles the parallel auth request againts a single host:port combination
func (bruter *SSH) handle(outChan chan model.Result, errChan chan model.Err) {
// defer func() {
// if x := recover(); x != nil {
// fmt.Println("PAAAANIIIICCCC")
// panic("!!!")
// }
// }()
defer bruter.wg.Done()
addr := fmt.Sprintf("%s:%d", bruter.Host, bruter.Port)
for c := range bruter.queue {
if !bruter.active.Get() { // possibility to get a recheck or counter
continue
}
time.Sleep(bruter.sleep)
ok, err := ConnectWithCredential(addr, c, bruter.timeout)
if err != nil {
// TODO(Chris): err handling should be better
errStr := err.Error()
shouldContinue := true
// ssh: handshake failed: read tcp 127.0.0.1:41946-\u003e127.0.0.1:14002: read: connection reset by peer
// ssh: handshake failed: EOF
// If password is wrong
// ssh: unable to authenticate, attempted methods [none], no supported methods remain
if strings.Contains(errStr, "no supported methods remain") {
if bruter.LogFailedAttempts {
log.Println("Failed for", addr, string(c.ToJson()))
}
continue
}
// dial tcp 178.254.26.222:15681: connect: connection refused
if bruter.StopIfNetErr && strings.Contains(errStr, "connect: connection refused") {
shouldContinue = false
}
if !shouldContinue {
log.Println("Disable Bruteforce (conn reset) for ", Name, bruter.Host)
bruter.active.Set(false)
}
// log err
errChan <- model.Err{
Error: errStr,
Host: bruter.Host,
Port: strconv.Itoa(bruter.Port),
Addr: addr,
}
}
// check if pw is successfull
if ok {
if bruter.StopIfSuccess {
bruter.active.Set(false)
}
// output
outChan <- model.Result{
Service: Name,
Host: bruter.Host,
Credential: c,
Port: strconv.Itoa(bruter.Port),
}
}
}
}
// GetAuthMethod returns the appropriate ssh.AuthMethod based on the provided Credentials
func GetAuthMethod(cred model.Credential) (ssh.AuthMethod, error) {
if sshKey, ok := cred.Data["ssh_key"]; ok {
var key ssh.Signer
var err error
if cred.Password == "" {
key, err = ssh.ParsePrivateKey([]byte(sshKey))
if err != nil {
log.Println("Error while parsing key", cred.User)
return nil, err
}
} else {
key, err = ssh.ParsePrivateKeyWithPassphrase([]byte(sshKey), []byte(cred.Password))
if err != nil {
log.Println("Error while parsing key", cred.User)
return nil, err
}
}
return ssh.PublicKeys(key), nil
}
return ssh.Password(cred.Password), nil
}
// ConnectWithCredential returns ok == true, if the credential work, else err is not nil
func ConnectWithCredential(addr string, cred model.Credential, timeout time.Duration) (ok bool, err error) {
authMethod, err := GetAuthMethod(cred)
if err != nil {
return
}
config := &ssh.ClientConfig{
User: cred.User,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: []ssh.AuthMethod{authMethod},
Timeout: timeout,
// TODO(chris): does not work :/
BannerCallback: ssh.BannerDisplayStderr(),
}
return Connect(addr, config)
}
// ConnectWithCredential returns ok == true, if the credential work, else err is not nil
func ConnectWithPassword(addr, user, passwd string, timeout time.Duration) (ok bool, err error) {
config := &ssh.ClientConfig{
User: user,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Auth: []ssh.AuthMethod{ssh.Password(passwd)},
Timeout: timeout,
// TODO(chris): does not work :/
BannerCallback: ssh.BannerDisplayStderr(),
}
return Connect(addr, config)
}
// Connect connects to the server using the provided ClientConfig
func Connect(addr string, config *ssh.ClientConfig) (ok bool, err error) {
cl, err := ssh.Dial("tcp", addr, config)
if err != nil {
return ok, err
}
// --[Connection successfull]--
ok = true
sess, err := cl.NewSession()
if err != nil {
return true, err
}
err = sess.Close()
if err != nil {
return ok, err
}
return true, nil
}