Skip to content

Commit e3ce590

Browse files
committed
Fix/Feat(SSH): SSH agentless procfs scanning now working properly
1 parent 5b80456 commit e3ce590

File tree

7 files changed

+222
-124
lines changed

7 files changed

+222
-124
lines changed

hash.go

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -153,35 +153,51 @@ func (m *MultiHasher) HashFile(path string) (map[HashType]string, error) {
153153
var err error
154154
var fSize int64
155155
var f io.ReadCloser
156-
var hashResults = make(map[HashType]string, len(m.todo))
156+
157157
if f, fSize, err = preCheckFilepath(path); err != nil {
158-
return hashResults, err
158+
return nil, err
159159
}
160160

161-
defer func() {
162-
_ = f.Close()
163-
}()
164-
165161
if fSize > int64(constMaxFileSize) {
166-
return hashResults, NewErrFileTooLarge(path, fSize)
162+
return nil, errors.Join(f.Close(), NewErrFileTooLarge(path, fSize))
167163
}
168164

169-
return m.Hash(f)
165+
res, herr := m.Hash(f)
166+
167+
return res, errors.Join(f.Close(), herr)
170168
}
171169

172-
func (cfg *config) runEnabledHashers(file *File) error {
170+
func (cfg *config) multHasher(file *File) *MultiHasher {
173171
if file.Checksums == nil {
174172
file.Checksums = new(Checksums)
175173
}
176174

177-
mh := NewMultiHasher(cfg.hashers...)
175+
return NewMultiHasher(cfg.hashers...)
176+
}
178177

179-
results, err := mh.HashFile(file.Path)
180-
if err != nil {
181-
return err
178+
func (f *File) setHashes(hashes map[HashType]string) {
179+
if hashes == nil {
180+
return
182181
}
183-
for ht, res := range results {
184-
file.Checksums.Set(ht, res)
182+
for ht, h := range hashes {
183+
f.Checksums.Set(ht, h)
185184
}
186-
return nil
185+
}
186+
187+
func (cfg *config) runEnabledHashersOnPath(file *File) error {
188+
mh := cfg.multHasher(file)
189+
190+
results, err := mh.HashFile(file.Path)
191+
file.setHashes(results)
192+
193+
return err
194+
}
195+
196+
func (cfg *config) runEnabledHashersOnBytes(file *File, r io.Reader) error {
197+
mh := cfg.multHasher(file)
198+
199+
results, err := mh.Hash(r)
200+
file.setHashes(results)
201+
202+
return err
187203
}

pkg/ssh/io.go

Lines changed: 71 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,111 @@
11
package ssh
22

33
import (
4+
"bytes"
5+
"context"
46
"errors"
57
"golang.org/x/crypto/ssh"
68
"io"
79
"path/filepath"
810
"strconv"
9-
"sync"
1011
)
1112

12-
func (s *SSH) procReadLink(sesh *ssh.Session, pid int) (procfs, abs string) {
13-
procFSPath := filepath.Join("/proc", strconv.Itoa(pid), "exe")
14-
var pthB = []byte(procFSPath)
15-
rlCmd := "readlink -f " + procFSPath
16-
var err error
17-
if pthB, err = s.Run(sesh, rlCmd); err != nil {
18-
s.verbLn("[io] readlink -f error: %s", err)
19-
pthB = []byte(procFSPath)
13+
const getPIDs = `bash -c 'for proc in /proc/*/exe; do if test -r "$proc" > /dev/null; then echo -n "$proc" | grep -v self | tr -d "/exeproc" | tr "\n" " "; fi; done'`
14+
15+
func (s *SSH) whoami(sesh *ssh.Session) (string, error) {
16+
var whoiam = ""
17+
usr, err := s.Run(sesh, "whoami")
18+
if usr != nil {
19+
whoiam = string(bytes.TrimSpace(usr))
2020
}
21-
return procFSPath, string(pthB)
21+
return whoiam, err
2222
}
2323

24-
// GetSessions returns a slice of SSH sessions.
25-
// It creates the sessions concurrently.
26-
func (s *SSH) GetSessions(i int) ([]*ssh.Session, error) {
27-
var wg = new(sync.WaitGroup)
28-
wg.Add(i)
29-
var seshi = make([]*ssh.Session, i)
30-
var errs = make([]error, i)
31-
for j := 0; j < i; j++ {
32-
if s.Closed() {
33-
s.verbLn("[session] parent is closed")
34-
return nil, io.ErrClosedPipe
24+
// GetPIDs returns a list of process IDs from the remote host that the user has access to.
25+
func (s *SSH) GetPIDs() ([]int, error) {
26+
var (
27+
pids = make([]int, 0)
28+
ps []byte
29+
err error
30+
sesh *ssh.Session
31+
)
32+
33+
if sesh, err = s.GetSession(context.Background()); err != nil {
34+
return nil, err
35+
}
36+
37+
if ps, err = s.Run(sesh, getPIDs); err != nil {
38+
return nil, err
39+
}
40+
41+
for _, p := range bytes.Fields(ps) {
42+
var pid int
43+
if pid, err = strconv.Atoi(string(p)); err == nil {
44+
pids = append(pids, pid)
3545
}
36-
go func(e int) {
37-
s.traceLn("[session] creating session %d", e)
38-
seshi[e], errs[e] = s.client.NewSession()
39-
wg.Done()
40-
}(j)
4146
}
42-
wg.Wait()
43-
return seshi, errors.Join(errs...)
47+
48+
s.verbLn("found %d PIDs with read permissions: %+v", len(pids), pids)
49+
50+
return pids, nil
4451
}
4552

46-
// CloseSessions closes a slice of SSH sessions.
47-
func (s *SSH) CloseSessions(sesh []*ssh.Session) error {
48-
var wg = new(sync.WaitGroup)
49-
wg.Add(len(sesh))
50-
var errs = make([]error, len(sesh))
51-
for j := 0; j < len(sesh); j++ {
52-
if s.Closed() {
53-
s.verbLn("[session] parent is closed")
54-
return io.ErrClosedPipe
55-
}
56-
go func(e int) {
57-
if sesh[e] == nil {
58-
s.traceLn("[session] session %d is nil", e)
59-
wg.Done()
60-
return
61-
}
62-
s.traceLn("[session] closing session %d", e)
63-
errs[e] = sesh[e].Close()
64-
wg.Done()
65-
}(j)
53+
func (s *SSH) procReadLink(sesh *ssh.Session, pid int) (procfs, abs string) {
54+
procFSPath := filepath.Join("/proc", strconv.Itoa(pid), "exe")
55+
var pthB = []byte(procFSPath)
56+
57+
var err error
58+
if pthB, err = s.Run(sesh, "readlink -f "+procFSPath); err != nil {
59+
pthB = []byte(procFSPath)
6660
}
67-
wg.Wait()
68-
return errors.Join(errs...)
61+
62+
s.verbLn("procfs path: %s", string(pthB))
63+
64+
return procFSPath, string(bytes.TrimSpace(pthB))
6965
}
7066

7167
// ReadProc reads the executable of a process from the remote host.
7268
func (s *SSH) ReadProc(pid int) (path string, data []byte, err error) {
73-
s.traceLn("[io] reading procfs, PID %d...", pid)
69+
s.traceLn("reading procfs, PID %d...", pid)
7470
if s.Closed() {
75-
s.verbLn("[io] parent is closed")
71+
s.verbLn("parent is closed")
7672
return "", nil, io.ErrClosedPipe
7773
}
7874

79-
seshi, err := s.GetSessions(2)
80-
if err != nil {
81-
return "", nil, err
75+
var seshi = make([]*ssh.Session, 2)
76+
ctx, cancel := context.WithTimeout(context.Background(), s.tout)
77+
for i := range seshi {
78+
var sesh *ssh.Session
79+
if sesh, err = s.GetSession(ctx); err != nil {
80+
cancel()
81+
return "", nil, err
82+
}
83+
seshi[i] = sesh
8284
}
8385

8486
proc, abs := s.procReadLink(seshi[0], pid)
8587
data, err = s.Run(seshi[1], "cat "+proc)
8688

89+
cancel()
8790
return abs, data, err
8891
}
8992

9093
// Run executes a command on the remote host.
9194
func (s *SSH) Run(sesh *ssh.Session, cmd string) (output []byte, err error) {
9295
s.verbLn("$ " + cmd)
93-
output, err = sesh.Output(cmd)
96+
if output, err = sesh.Output(cmd); err != nil {
97+
s.verbLn("run error: %s", err.Error())
98+
}
99+
if errors.Is(err, io.EOF) {
100+
err = nil
101+
}
102+
94103
cerr := sesh.Close()
104+
if errors.Is(cerr, io.EOF) {
105+
cerr = nil
106+
}
107+
108+
s.verbLn("\tresulting output: %d bytes", len(output))
109+
95110
return output, errors.Join(err, cerr)
96111
}

pkg/ssh/session.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package ssh
2+
3+
import (
4+
"context"
5+
"golang.org/x/crypto/ssh"
6+
"io"
7+
"time"
8+
)
9+
10+
func (s *SSH) createSessions() {
11+
s.traceLn("creating sessions...")
12+
for {
13+
if s.closed.Load() || s.client == nil {
14+
s.traceLn("end of session creation")
15+
return
16+
}
17+
time.Sleep(100 * time.Millisecond)
18+
sesh, err := s.client.NewSession()
19+
if err != nil {
20+
s.verbLn("error creating session: %v", err)
21+
time.Sleep(1 * time.Second)
22+
continue
23+
}
24+
select {
25+
case s.sessions <- sesh:
26+
s.traceLn("created session %p", sesh)
27+
default:
28+
_ = sesh.Close()
29+
time.Sleep(1 * time.Second)
30+
}
31+
}
32+
}
33+
34+
// GetSession returns a session from the SSH connection.
35+
func (s *SSH) GetSession(ctx context.Context) (*ssh.Session, error) {
36+
if s.closed.Load() {
37+
return nil, io.ErrClosedPipe
38+
}
39+
select {
40+
case <-ctx.Done():
41+
return nil, ctx.Err()
42+
case sesh := <-s.sessions:
43+
return sesh, nil
44+
}
45+
}

0 commit comments

Comments
 (0)