nuclei/pkg/js/libs/rsync/rsync.go

214 lines
6.1 KiB
Go

package rsync
import (
"bytes"
"context"
"fmt"
"log/slog"
"net"
"strconv"
"time"
rsynclib "github.com/Mzack9999/go-rsync/rsync"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/rsync"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
type (
// RsyncClient is a client for RSYNC servers.
// Internally client uses https://github.com/gokrazy/rsync driver.
// @example
// ```javascript
// const rsync = require('nuclei/rsync');
// const client = new rsync.RsyncClient();
// ```
RsyncClient struct{}
// IsRsyncResponse is the response from the IsRsync function.
// this is returned by IsRsync function.
// @example
// ```javascript
// const rsync = require('nuclei/rsync');
// const isRsync = rsync.IsRsync('acme.com', 873);
// log(toJSON(isRsync));
// ```
IsRsyncResponse struct {
IsRsync bool
Banner string
}
// ListSharesResponse is the response from the ListShares function.
// this is returned by ListShares function.
// @example
// ```javascript
// const rsync = require('nuclei/rsync');
// const client = new rsync.RsyncClient();
// const listShares = client.ListShares('acme.com', 873);
// log(toJSON(listShares));
RsyncListResponse struct {
Modules []string
Files []string
Output string
}
)
func connectWithFastDialer(executionId string, host string, port int) (net.Conn, error) {
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return nil, fmt.Errorf("dialers not initialized for %s", executionId)
}
return dialer.Fastdialer.Dial(context.Background(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
}
// IsRsync checks if a host is running a Rsync server.
// @example
// ```javascript
// const rsync = require('nuclei/rsync');
// const isRsync = rsync.IsRsync('acme.com', 873);
// log(toJSON(isRsync));
// ```
func IsRsync(ctx context.Context, host string, port int) (IsRsyncResponse, error) {
executionId := ctx.Value("executionId").(string)
return memoizedisRsync(executionId, host, port)
}
// @memo
func isRsync(executionId string, host string, port int) (IsRsyncResponse, error) {
resp := IsRsyncResponse{}
timeout := 5 * time.Second
conn, err := connectWithFastDialer(executionId, host, port)
if err != nil {
return resp, err
}
defer func() {
_ = conn.Close()
}()
rsyncPlugin := rsync.RSYNCPlugin{}
service, err := rsyncPlugin.Run(conn, timeout, plugins.Target{Host: host})
if err != nil {
return resp, nil
}
if service == nil {
return resp, nil
}
resp.Banner = service.Version
resp.IsRsync = true
return resp, nil
}
// ListModules lists the modules of a Rsync server.
// @example
// ```javascript
// const rsync = require('nuclei/rsync');
// const client = new rsync.RsyncClient();
// const listModules = client.ListModules('acme.com', 873, 'username', 'password');
// log(toJSON(listModules));
// ```
func (c *RsyncClient) ListModules(ctx context.Context, host string, port int, username string, password string) (RsyncListResponse, error) {
executionId := ctx.Value("executionId").(string)
return listModules(executionId, host, port, username, password)
}
// ListShares lists the shares of a Rsync server.
// @example
// ```javascript
// const rsync = require('nuclei/rsync');
// const client = new rsync.RsyncClient();
// const listShares = client.ListFilesInModule('acme.com', 873, 'username', 'password', '/');
// log(toJSON(listShares));
// ```
func (c *RsyncClient) ListFilesInModule(ctx context.Context, host string, port int, username string, password string, module string) (RsyncListResponse, error) {
executionId := ctx.Value("executionId").(string)
return listFilesInModule(executionId, host, port, username, password, module)
}
func listModules(executionId string, host string, port int, username string, password string) (RsyncListResponse, error) {
fastDialer := protocolstate.GetDialersWithId(executionId)
if fastDialer == nil {
return RsyncListResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
}
address := net.JoinHostPort(host, strconv.Itoa(port))
// Create a bytes buffer for logging
var logBuffer bytes.Buffer
// Create a custom slog handler that writes to the buffer
logHandler := slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
// Create a logger that writes to our buffer
logger := slog.New(logHandler)
sr, err := rsynclib.ListModules(address,
rsynclib.WithClientAuth(username, password),
rsynclib.WithLogger(logger),
rsynclib.WithFastDialer(fastDialer.Fastdialer),
)
if err != nil {
return RsyncListResponse{}, fmt.Errorf("connect failed: %v", err)
}
result := RsyncListResponse{
Modules: make([]string, len(sr)),
Output: logBuffer.String(),
}
for i, item := range sr {
result.Modules[i] = string(item.Name)
}
return result, nil
}
func listFilesInModule(executionId string, host string, port int, username string, password string, module string) (RsyncListResponse, error) {
fastDialer := protocolstate.GetDialersWithId(executionId)
if fastDialer == nil {
return RsyncListResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
}
address := net.JoinHostPort(host, strconv.Itoa(port))
// Create a bytes buffer for logging
var logBuffer bytes.Buffer
// Create a custom slog handler that writes to the buffer
logHandler := slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
// Create a logger that writes to our buffer
logger := slog.New(logHandler)
sr, err := rsynclib.SocketClient(nil, address, module, ".",
rsynclib.WithClientAuth(username, password),
rsynclib.WithLogger(logger),
rsynclib.WithFastDialer(fastDialer.Fastdialer),
)
if err != nil {
return RsyncListResponse{}, fmt.Errorf("connect failed: %v", err)
}
// Try to list files to test authentication
list, err := sr.List()
if err != nil {
return RsyncListResponse{}, fmt.Errorf("authentication failed: %v", err)
}
result := RsyncListResponse{
Files: make([]string, len(list)),
Output: logBuffer.String(),
}
for i, item := range list {
result.Files[i] = string(item.Path)
}
return result, nil
}