Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/js/libs/ssh/ssh.go
2070 views
1
package ssh
2
3
import (
4
"context"
5
"fmt"
6
"strings"
7
"time"
8
9
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
10
"github.com/projectdiscovery/utils/errkit"
11
"github.com/zmap/zgrab2/lib/ssh"
12
)
13
14
type (
15
// SSHClient is a client for SSH servers.
16
// Internally client uses github.com/zmap/zgrab2/lib/ssh driver.
17
// @example
18
// ```javascript
19
// const ssh = require('nuclei/ssh');
20
// const client = new ssh.SSHClient();
21
// ```
22
SSHClient struct {
23
connection *ssh.Client
24
timeout time.Duration
25
}
26
)
27
28
// SetTimeout sets the timeout for the SSH connection in seconds
29
// @example
30
// ```javascript
31
// const ssh = require('nuclei/ssh');
32
// const client = new ssh.SSHClient();
33
// client.SetTimeout(10);
34
// ```
35
func (c *SSHClient) SetTimeout(sec int) {
36
c.timeout = time.Duration(sec) * time.Second
37
}
38
39
// Connect tries to connect to provided host and port
40
// with provided username and password with ssh.
41
// Returns state of connection and error. If error is not nil,
42
// state will be false
43
// @example
44
// ```javascript
45
// const ssh = require('nuclei/ssh');
46
// const client = new ssh.SSHClient();
47
// const connected = client.Connect('acme.com', 22, 'username', 'password');
48
// ```
49
func (c *SSHClient) Connect(ctx context.Context, host string, port int, username, password string) (bool, error) {
50
executionId := ctx.Value("executionId").(string)
51
conn, err := connect(&connectOptions{
52
Host: host,
53
Port: port,
54
User: username,
55
Password: password,
56
ExecutionId: executionId,
57
})
58
if err != nil {
59
return false, err
60
}
61
c.connection = conn
62
63
return true, nil
64
}
65
66
// ConnectWithKey tries to connect to provided host and port
67
// with provided username and private_key.
68
// Returns state of connection and error. If error is not nil,
69
// state will be false
70
// @example
71
// ```javascript
72
// const ssh = require('nuclei/ssh');
73
// const client = new ssh.SSHClient();
74
// const privateKey = `-----BEGIN RSA PRIVATE KEY----- ...`;
75
// const connected = client.ConnectWithKey('acme.com', 22, 'username', privateKey);
76
// ```
77
func (c *SSHClient) ConnectWithKey(ctx context.Context, host string, port int, username, key string) (bool, error) {
78
executionId := ctx.Value("executionId").(string)
79
conn, err := connect(&connectOptions{
80
Host: host,
81
Port: port,
82
User: username,
83
PrivateKey: key,
84
ExecutionId: executionId,
85
})
86
87
if err != nil {
88
return false, err
89
}
90
c.connection = conn
91
92
return true, nil
93
}
94
95
// ConnectSSHInfoMode tries to connect to provided host and port
96
// with provided host and port
97
// Returns HandshakeLog and error. If error is not nil,
98
// state will be false
99
// HandshakeLog is a struct that contains information about the
100
// ssh connection
101
// @example
102
// ```javascript
103
// const ssh = require('nuclei/ssh');
104
// const client = new ssh.SSHClient();
105
// const info = client.ConnectSSHInfoMode('acme.com', 22);
106
// log(to_json(info));
107
// ```
108
func (c *SSHClient) ConnectSSHInfoMode(ctx context.Context, host string, port int) (*ssh.HandshakeLog, error) {
109
executionId := ctx.Value("executionId").(string)
110
return memoizedconnectSSHInfoMode(&connectOptions{
111
Host: host,
112
Port: port,
113
ExecutionId: executionId,
114
})
115
}
116
117
// Run tries to open a new SSH session, then tries to execute
118
// the provided command in said session
119
// Returns string and error. If error is not nil,
120
// state will be false
121
// The string contains the command output
122
// @example
123
// ```javascript
124
// const ssh = require('nuclei/ssh');
125
// const client = new ssh.SSHClient();
126
// client.Connect('acme.com', 22, 'username', 'password');
127
// const output = client.Run('id');
128
// log(output);
129
// ```
130
func (c *SSHClient) Run(cmd string) (string, error) {
131
if c.connection == nil {
132
return "", errkit.New("no connection")
133
}
134
session, err := c.connection.NewSession()
135
if err != nil {
136
return "", err
137
}
138
defer func() {
139
_ = session.Close()
140
}()
141
142
data, err := session.Output(cmd)
143
if err != nil {
144
return "", err
145
}
146
147
return string(data), nil
148
}
149
150
// Close closes the SSH connection and destroys the client
151
// Returns the success state and error. If error is not nil,
152
// state will be false
153
// @example
154
// ```javascript
155
// const ssh = require('nuclei/ssh');
156
// const client = new ssh.SSHClient();
157
// client.Connect('acme.com', 22, 'username', 'password');
158
// const closed = client.Close();
159
// ```
160
func (c *SSHClient) Close() (bool, error) {
161
if err := c.connection.Close(); err != nil {
162
return false, err
163
}
164
return true, nil
165
}
166
167
// unexported functions
168
type connectOptions struct {
169
Host string
170
Port int
171
User string
172
Password string
173
PrivateKey string
174
Timeout time.Duration // default 10s
175
ExecutionId string
176
}
177
178
func (c *connectOptions) validate() error {
179
if c.Host == "" {
180
return errkit.New("host is required")
181
}
182
if c.Port <= 0 {
183
return errkit.New("port is required")
184
}
185
if !protocolstate.IsHostAllowed(c.ExecutionId, c.Host) {
186
// host is not valid according to network policy
187
return protocolstate.ErrHostDenied.Msgf(c.Host)
188
}
189
if c.Timeout == 0 {
190
c.Timeout = 10 * time.Second
191
}
192
return nil
193
}
194
195
// @memo
196
func connectSSHInfoMode(opts *connectOptions) (*ssh.HandshakeLog, error) {
197
if err := opts.validate(); err != nil {
198
return nil, err
199
}
200
201
data := new(ssh.HandshakeLog)
202
203
sshConfig := ssh.MakeSSHConfig()
204
sshConfig.Timeout = 10 * time.Second
205
sshConfig.ConnLog = data
206
sshConfig.DontAuthenticate = true
207
sshConfig.BannerCallback = func(banner string) error {
208
data.Banner = strings.TrimSpace(banner)
209
return nil
210
}
211
rhost := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
212
client, err := ssh.Dial("tcp", rhost, sshConfig)
213
if err != nil {
214
return nil, err
215
}
216
defer func() {
217
_ = client.Close()
218
}()
219
220
return data, nil
221
}
222
223
func connect(opts *connectOptions) (*ssh.Client, error) {
224
if err := opts.validate(); err != nil {
225
return nil, err
226
}
227
228
conf := &ssh.ClientConfig{
229
User: opts.User,
230
Auth: []ssh.AuthMethod{},
231
Timeout: opts.Timeout,
232
}
233
if len(opts.Password) > 0 {
234
conf.Auth = append(conf.Auth, ssh.Password(opts.Password))
235
}
236
if len(opts.PrivateKey) > 0 {
237
signer, err := ssh.ParsePrivateKey([]byte(opts.PrivateKey))
238
if err != nil {
239
return nil, err
240
}
241
conf.Auth = append(conf.Auth, ssh.PublicKeys(signer))
242
}
243
244
client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", opts.Host, opts.Port), conf)
245
if err != nil {
246
return nil, err
247
}
248
return client, nil
249
}
250
251