Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: ci

on:
pull_request:
paths-ignore:
- '**.md'
push:
branches:
- main
paths-ignore:
- '**.md'

permissions:
contents: read

jobs:
lint:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: "go.mod"
cache: false

- name: Ensure go modules are tidy
run: |
go mod tidy
if [[ -n $(git status -s) ]] ; then
echo
echo -e "\e[31mRunning 'go mod tidy' changes the current setting"
echo -e "\e[31mEnsure to include updated go.mod and go.sum in this PR."
echo -e "\e[31mThis is usually done by running 'go mod tidy'\e[0m"
git status -s
git diff --color
exit 1
fi

- name: Run linters
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
with:
version: latest

build:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: "go.mod"
cache: false

- run: go mod download

- run: make build

- name: Build gendetections
run: go build -v -trimpath ./scripts/gendetections

test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version-file: "go.mod"
cache: false

- run: go mod download

- run: make test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
**/.DS_Store
detections.json
9 changes: 8 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,22 @@ ENV GOARCH=$TARGETARCH
ENV GOOS=linux

RUN go build -v -trimpath -ldflags="-w -s -X 'main.version=$(git describe --abbrev=0 --tags | sed s/v//)'" -o /go/bin/shell2http .
RUN go build -v -trimpath -o /go/bin/gendetections ./scripts/gendetections

# Generate detections.json from the base image's scripts
FROM quay.io/crowdstrike/detection-container AS detector
COPY --from=builder /go/bin/gendetections /gendetections
RUN /gendetections /home/eval/bin > /detections.json

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

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

COPY --from=detector /detections.json /detections.json
COPY --from=builder /go/bin/shell2http /shell2http
COPY entrypoint.sh /
COPY images /images
COPY --from=builder /go/bin/shell2http /shell2http

EXPOSE 8080

Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ run:
build:
go build .

generate-detections:
go run ./scripts/gendetections $(SCRIPTS_DIR)

update-from-github:
go install github.com/msoap/$(APP_NAME)@latest

Expand Down
2 changes: 2 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type Config struct {
includeStderr bool // also returns output written to stderr (default is stdout only)
intServerErr bool // return 500 error if shell status code != 0
formCheckRe *regexp.Regexp // regexp for check form fields
commandsFile string // JSON file with path/command/description entries
}

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

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

Expand Down
14 changes: 1 addition & 13 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,4 @@ cd /home/eval
# Mark as CS testcontainer
sh -c echo CS_testcontainer starting

exec /shell2http -show-errors -include-stderr \
/ps "ps aux" \
/rootkit ./bin/Defense_Evasion_via_Rootkit.sh \
/masquerading ./bin/Defense_Evasion_via_Masquerading.sh \
/data_exfiltration ./bin/Exfiltration_via_Exfiltration_Over_Alternative_Protocol.sh \
/reverse_shell_trojan './bin/Reverse_Shell_Trojan.sh' \
/deploy_malware './bin/evil/Linux_Malware_High' \
/reverse_shell ./bin/Command_Control_via_Remote_Access.sh \
/reverse_shell-obfuscated ./bin/Command_Control_via_Remote_Access-obfuscated.sh \
/credentials_dumping ./bin/Credential_Access_via_Credential_Dumping.sh \
/credentials_dumping_collection ./bin/Collection_via_Automated_Collection.sh \
/suspicious_commands ./bin/Execution_via_Command-Line_Interface.sh \
/container_drift ./bin/ContainerDrift_Via_File_Creation_and_Execution.sh
exec /shell2http -show-errors -include-stderr -commands-file /detections.json
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/msoap/shell2http

go 1.18
go 1.26

require (
github.com/mattn/go-shellwords v1.0.12
Expand Down
109 changes: 109 additions & 0 deletions scripts/gendetections/gendetections.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package main

import (
"bufio"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)

type commandEntry struct {
Path string `json:"path"`
Command string `json:"command"`
Description string `json:"description,omitempty"`
}

func parseScriptHeaders(path string) (shortname, description string, err error) {
f, err := os.Open(path)
if err != nil {
return "", "", fmt.Errorf("open %s: %w", path, err)
}
defer func() { _ = f.Close() }()

scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, "#") {
break
}
if v, ok := strings.CutPrefix(line, "# Shortname:"); ok {
shortname = strings.TrimSpace(v)
} else if v, ok := strings.CutPrefix(line, "# Description:"); ok {
description = strings.TrimSpace(v)
}
}
if err := scanner.Err(); err != nil {
return "", "", fmt.Errorf("read %s: %w", path, err)
}
return shortname, description, nil
}

func scanDirectory(dir string) ([]commandEntry, error) {
entries, err := os.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("read directory %s: %w", dir, err)
}

seen := map[string]string{}
var cmds []commandEntry
for _, e := range entries {
if e.IsDir() || !strings.HasSuffix(e.Name(), ".sh") {
continue
}

path := filepath.Join(dir, e.Name())
shortname, description, err := parseScriptHeaders(path)
if err != nil {
return nil, err
}
if shortname == "" || description == "" {
continue
}

urlPath := "/" + shortname
if prev, ok := seen[urlPath]; ok {
return nil, fmt.Errorf("duplicate path %q: %s and %s", urlPath, prev, e.Name())
}
seen[urlPath] = e.Name()

cmds = append(cmds, commandEntry{
Path: urlPath,
Command: "./" + filepath.Join("bin", e.Name()),
Description: description,
})
}

if len(cmds) == 0 {
return nil, fmt.Errorf("no valid detection scripts found in %s", dir)
}

return cmds, nil
}

func run() error {
if len(os.Args) != 2 {
return fmt.Errorf("usage: gendetections <scripts-dir>")
}

cmds, err := scanDirectory(os.Args[1])
if err != nil {
return err
}

enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(cmds); err != nil {
return fmt.Errorf("encode JSON: %w", err)
}

return nil
}

func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "gendetections: %s\n", err)
os.Exit(1)
}
}
Loading
Loading