mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
153 lines
4.2 KiB
Go
153 lines
4.2 KiB
Go
//go:build linux && !no_local
|
|
|
|
package local
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/shadow1ng/fscan/common"
|
|
)
|
|
|
|
func TestLocalPluginConstructors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
got Plugin
|
|
}{
|
|
{name: "cleaner", got: NewCleanerPlugin()},
|
|
{name: "crontask", got: NewCronTaskPlugin()},
|
|
{name: "forwardshell", got: NewForwardShellPlugin()},
|
|
{name: "keylogger", got: NewKeyloggerPlugin()},
|
|
{name: "ldpreload", got: NewLDPreloadPlugin()},
|
|
{name: "reverseshell", got: NewReverseShellPlugin()},
|
|
{name: "socks5proxy", got: NewSocks5ProxyPlugin()},
|
|
{name: "sshkey", got: NewSSHKeyPlugin()},
|
|
{name: "systemdservice", got: NewSystemdServicePlugin()},
|
|
{name: "systeminfo", got: NewSystemInfoPlugin()},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.got == nil {
|
|
t.Fatal("constructor returned nil")
|
|
}
|
|
if got := tt.got.Name(); got != tt.name {
|
|
t.Fatalf("Name() = %q, want %q", got, tt.name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCronTaskScriptDetectionAndJobs(t *testing.T) {
|
|
plugin := NewCronTaskPlugin()
|
|
for _, name := range []string{"agent.sh", "agent.bash", "agent.zsh"} {
|
|
plugin.targetFile = name
|
|
if !plugin.isScriptFile() {
|
|
t.Fatalf("%s should be treated as script", name)
|
|
}
|
|
}
|
|
|
|
plugin.targetFile = "agent.bin"
|
|
if plugin.isScriptFile() {
|
|
t.Fatal("binary target should not be treated as script")
|
|
}
|
|
|
|
plugin.targetFile = "agent.sh"
|
|
jobs := plugin.generateCronJobs("/tmp/agent.sh")
|
|
if len(jobs) != 4 {
|
|
t.Fatalf("job count = %d, want 4", len(jobs))
|
|
}
|
|
for _, job := range jobs {
|
|
if !strings.Contains(job, "bash /tmp/agent.sh >/dev/null 2>&1") {
|
|
t.Fatalf("script cron job missing bash wrapper: %q", job)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLDPreloadValidFileDetection(t *testing.T) {
|
|
dir := t.TempDir()
|
|
plugin := NewLDPreloadPlugin()
|
|
|
|
soPath := filepath.Join(dir, "libhook.so")
|
|
if err := os.WriteFile(soPath, []byte("not actually elf"), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !plugin.isValidFile(soPath) {
|
|
t.Fatal(".so file should be accepted by extension")
|
|
}
|
|
|
|
elfPath := filepath.Join(dir, "payload.bin")
|
|
if err := os.WriteFile(elfPath, []byte{0x7f, 'E', 'L', 'F', 0x02}, 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !plugin.isValidFile(elfPath) {
|
|
t.Fatal("ELF magic file should be accepted")
|
|
}
|
|
|
|
textPath := filepath.Join(dir, "payload.txt")
|
|
if err := os.WriteFile(textPath, []byte("plain text"), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if plugin.isValidFile(textPath) {
|
|
t.Fatal("plain text file should not be accepted")
|
|
}
|
|
}
|
|
|
|
func TestKeyloggerBufferAndFileHelpers(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "keys.log")
|
|
session := common.NewScanSession(common.NewConfig(), common.NewState(), &common.FlagVars{})
|
|
plugin := NewKeyloggerPlugin()
|
|
|
|
if err := plugin.checkOutputFilePermissions(path); err != nil {
|
|
t.Fatalf("checkOutputFilePermissions error = %v", err)
|
|
}
|
|
if _, err := os.Stat(path); err != nil {
|
|
t.Fatalf("output file was not created: %v", err)
|
|
}
|
|
|
|
if err := plugin.saveKeysToFile(path, session); err != nil {
|
|
t.Fatalf("save empty keys error = %v", err)
|
|
}
|
|
|
|
plugin.addKeyToBuffer("A")
|
|
plugin.addKeyToBuffer("B")
|
|
if len(plugin.keyBuffer) != 2 {
|
|
t.Fatalf("key buffer length = %d, want 2", len(plugin.keyBuffer))
|
|
}
|
|
if err := plugin.saveKeysToFile(path, session); err != nil {
|
|
t.Fatalf("save keys error = %v", err)
|
|
}
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(string(content), "A") || !strings.Contains(string(content), "B") {
|
|
t.Fatalf("saved key log missing entries: %q", content)
|
|
}
|
|
}
|
|
|
|
func TestShellUtilityHelpers(t *testing.T) {
|
|
prompt := NewForwardShellPlugin().getPrompt()
|
|
if !strings.HasSuffix(prompt, "$ ") && !strings.HasSuffix(prompt, "> ") && !strings.HasSuffix(prompt, "# ") {
|
|
t.Fatalf("unexpected prompt suffix: %q", prompt)
|
|
}
|
|
|
|
if dir := getCurrentDir(); dir == "" || dir == "unknown" {
|
|
t.Fatalf("getCurrentDir() = %q", dir)
|
|
}
|
|
|
|
pub, priv, err := NewSSHKeyPlugin().generateKeyPair()
|
|
if err != nil {
|
|
t.Fatalf("generateKeyPair error = %v", err)
|
|
}
|
|
if !strings.HasPrefix(pub, "ssh-ed25519 ") {
|
|
t.Fatalf("public key should be ssh-ed25519, got %q", pub)
|
|
}
|
|
if !strings.Contains(priv, "OPENSSH PRIVATE KEY") {
|
|
t.Fatal("private key should be OpenSSH PEM")
|
|
}
|
|
}
|