Skip to content

Commit c4af90f

Browse files
committed
feat: auto generate detection scripts
1 parent f3ac3da commit c4af90f

11 files changed

Lines changed: 567 additions & 30 deletions

File tree

.github/workflows/ci.yml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: ci
2+
3+
on:
4+
pull_request:
5+
paths-ignore:
6+
- '**.md'
7+
push:
8+
branches:
9+
- main
10+
paths-ignore:
11+
- '**.md'
12+
13+
permissions:
14+
contents: read
15+
16+
jobs:
17+
lint:
18+
runs-on: ubuntu-latest
19+
timeout-minutes: 5
20+
steps:
21+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
22+
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
23+
with:
24+
go-version-file: "go.mod"
25+
cache: false
26+
27+
- name: Ensure go modules are tidy
28+
run: |
29+
go mod tidy
30+
if [[ -n $(git status -s) ]] ; then
31+
echo
32+
echo -e "\e[31mRunning 'go mod tidy' changes the current setting"
33+
echo -e "\e[31mEnsure to include updated go.mod and go.sum in this PR."
34+
echo -e "\e[31mThis is usually done by running 'go mod tidy'\e[0m"
35+
git status -s
36+
git diff --color
37+
exit 1
38+
fi
39+
40+
- name: Run linters
41+
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
42+
with:
43+
version: latest
44+
45+
build:
46+
runs-on: ubuntu-latest
47+
timeout-minutes: 15
48+
steps:
49+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
50+
51+
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
52+
with:
53+
go-version-file: "go.mod"
54+
cache: false
55+
56+
- run: go mod download
57+
58+
- run: make build
59+
60+
- name: Build gendetections
61+
run: go build -v -trimpath ./scripts/gendetections
62+
63+
test:
64+
runs-on: ubuntu-latest
65+
timeout-minutes: 15
66+
steps:
67+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
68+
69+
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
70+
with:
71+
go-version-file: "go.mod"
72+
cache: false
73+
74+
- run: go mod download
75+
76+
- run: make test

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
**/.DS_Store
2+
detections.json

Dockerfile

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,22 @@ ENV GOARCH=$TARGETARCH
1010
ENV GOOS=linux
1111

1212
RUN go build -v -trimpath -ldflags="-w -s -X 'main.version=$(git describe --abbrev=0 --tags | sed s/v//)'" -o /go/bin/shell2http .
13+
RUN go build -v -trimpath -o /go/bin/gendetections ./scripts/gendetections
14+
15+
# Generate detections.json from the base image's scripts
16+
FROM quay.io/crowdstrike/detection-container AS detector
17+
COPY --from=builder /go/bin/gendetections /gendetections
18+
RUN /gendetections /home/eval/bin > /detections.json
1319

1420
# final image
1521
FROM quay.io/crowdstrike/detection-container
1622

1723
LABEL org.opencontainers.image.source="https://github.com/CrowdStrike/vulnapp"
1824

25+
COPY --from=detector /detections.json /detections.json
26+
COPY --from=builder /go/bin/shell2http /shell2http
1927
COPY entrypoint.sh /
2028
COPY images /images
21-
COPY --from=builder /go/bin/shell2http /shell2http
2229

2330
EXPOSE 8080
2431

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ run:
1010
build:
1111
go build .
1212

13+
generate-detections:
14+
go run ./scripts/gendetections $(SCRIPTS_DIR)
15+
1316
update-from-github:
1417
go install github.com/msoap/$(APP_NAME)@latest
1518

config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ type Config struct {
6969
includeStderr bool // also returns output written to stderr (default is stdout only)
7070
intServerErr bool // return 500 error if shell status code != 0
7171
formCheckRe *regexp.Regexp // regexp for check form fields
72+
commandsFile string // JSON file with path/command/description entries
7273
}
7374

7475
// getConfig - parse arguments
@@ -108,6 +109,7 @@ func getConfig() (*Config, error) {
108109
flag.StringVar(&cfg.key, "key", "", "SSL private key `/path/...`")
109110
flag.Var(&cfg.auth, "basic-auth", "setup HTTP Basic Authentication (\"user_name:password\"), can be used several times")
110111
flag.IntVar(&cfg.timeout, "timeout", 0, "set `timeout` for execute shell command (in seconds)")
112+
flag.StringVar(&cfg.commandsFile, "commands-file", "", "JSON `file` with path/command/description entries")
111113

112114
formCheck := flag.String("form-check", "", "regexp for check form fields (pass only vars that match the regexp)")
113115

entrypoint.sh

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,4 @@ cd /home/eval
77
# Mark as CS testcontainer
88
sh -c echo CS_testcontainer starting
99

10-
exec /shell2http -show-errors -include-stderr \
11-
/ps "ps aux" \
12-
/rootkit ./bin/Defense_Evasion_via_Rootkit.sh \
13-
/masquerading ./bin/Defense_Evasion_via_Masquerading.sh \
14-
/data_exfiltration ./bin/Exfiltration_via_Exfiltration_Over_Alternative_Protocol.sh \
15-
/reverse_shell_trojan './bin/Reverse_Shell_Trojan.sh' \
16-
/deploy_malware './bin/evil/Linux_Malware_High' \
17-
/reverse_shell ./bin/Command_Control_via_Remote_Access.sh \
18-
/reverse_shell-obfuscated ./bin/Command_Control_via_Remote_Access-obfuscated.sh \
19-
/credentials_dumping ./bin/Credential_Access_via_Credential_Dumping.sh \
20-
/credentials_dumping_collection ./bin/Collection_via_Automated_Collection.sh \
21-
/suspicious_commands ./bin/Execution_via_Command-Line_Interface.sh \
22-
/container_drift ./bin/ContainerDrift_Via_File_Creation_and_Execution.sh
10+
exec /shell2http -show-errors -include-stderr -commands-file /detections.json

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/msoap/shell2http
22

3-
go 1.18
3+
go 1.26
44

55
require (
66
github.com/mattn/go-shellwords v1.0.12
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
)
11+
12+
type commandEntry struct {
13+
Path string `json:"path"`
14+
Command string `json:"command"`
15+
Description string `json:"description,omitempty"`
16+
}
17+
18+
func parseScriptHeaders(path string) (shortname, description string, err error) {
19+
f, err := os.Open(path)
20+
if err != nil {
21+
return "", "", fmt.Errorf("open %s: %w", path, err)
22+
}
23+
defer f.Close()
24+
25+
scanner := bufio.NewScanner(f)
26+
for scanner.Scan() {
27+
line := scanner.Text()
28+
if !strings.HasPrefix(line, "#") {
29+
break
30+
}
31+
if v, ok := strings.CutPrefix(line, "# Shortname:"); ok {
32+
shortname = strings.TrimSpace(v)
33+
} else if v, ok := strings.CutPrefix(line, "# Description:"); ok {
34+
description = strings.TrimSpace(v)
35+
}
36+
}
37+
if err := scanner.Err(); err != nil {
38+
return "", "", fmt.Errorf("read %s: %w", path, err)
39+
}
40+
return shortname, description, nil
41+
}
42+
43+
func scanDirectory(dir string) ([]commandEntry, error) {
44+
entries, err := os.ReadDir(dir)
45+
if err != nil {
46+
return nil, fmt.Errorf("read directory %s: %w", dir, err)
47+
}
48+
49+
seen := map[string]string{}
50+
var cmds []commandEntry
51+
for _, e := range entries {
52+
if e.IsDir() || !strings.HasSuffix(e.Name(), ".sh") {
53+
continue
54+
}
55+
56+
path := filepath.Join(dir, e.Name())
57+
shortname, description, err := parseScriptHeaders(path)
58+
if err != nil {
59+
return nil, err
60+
}
61+
if shortname == "" || description == "" {
62+
continue
63+
}
64+
65+
urlPath := "/" + shortname
66+
if prev, ok := seen[urlPath]; ok {
67+
return nil, fmt.Errorf("duplicate path %q: %s and %s", urlPath, prev, e.Name())
68+
}
69+
seen[urlPath] = e.Name()
70+
71+
cmds = append(cmds, commandEntry{
72+
Path: urlPath,
73+
Command: "./" + filepath.Join("bin", e.Name()),
74+
Description: description,
75+
})
76+
}
77+
78+
if len(cmds) == 0 {
79+
return nil, fmt.Errorf("no valid detection scripts found in %s", dir)
80+
}
81+
82+
return cmds, nil
83+
}
84+
85+
func run() error {
86+
if len(os.Args) != 2 {
87+
return fmt.Errorf("usage: gendetections <scripts-dir>")
88+
}
89+
90+
cmds, err := scanDirectory(os.Args[1])
91+
if err != nil {
92+
return err
93+
}
94+
95+
enc := json.NewEncoder(os.Stdout)
96+
enc.SetIndent("", " ")
97+
if err := enc.Encode(cmds); err != nil {
98+
return fmt.Errorf("encode JSON: %w", err)
99+
}
100+
101+
return nil
102+
}
103+
104+
func main() {
105+
if err := run(); err != nil {
106+
fmt.Fprintf(os.Stderr, "gendetections: %s\n", err)
107+
os.Exit(1)
108+
}
109+
}

0 commit comments

Comments
 (0)