aboutsummaryrefslogtreecommitdiff
path: root/setup-command-canaries.sh
blob: 60324bd67047e44251177605815a72f3b3631aa0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#!/bin/bash
set -e

if ! command -v curl &>/dev/null; then
    printf "%s\n" "[err] curl not found"
    exit 1
fi

function usage() {
    printf "%s\n" \
        "set up command canaries for early intrusion detection via webhook notifications" \
        "usage: $(basename ${0}) <args>" \
        "" \
        "options" \
        "-c <cmd>        commands to monitor; repeatable, e.g., -c whoami -c id" \
        "-s <service>    notification service: discord|slack" \
        "-u <url>        webhook url" \
        "-m <method>     http method, default POST" \
        "-x <header>     webhook headers; repeatable, e.g., -x 'key1: value1' -x 'key2: value2'" \
        "-t <channel>    channel or destination id for notifications" \
        "-h              print this help message and exit" \
        "" \
        "examples" \
        "discord" \
        "$(basename ${0}) -s discord -c whoami -c id -u https://discord.com/api/webhooks/xxx" \
        "" \
        "slack" \
        "$(basename ${0}) -s slack -c whoami -c id -u https://slack.com/api/chat.postmessage -t mychannel -x 'authorization: bearer xoxb-a-b-c'" \
        "" \
        "output" \
        "generates canaries.txt with shell functions to append to /etc/bash.bashrc or ~/.bashrc."
    exit 1
}

function validate_url() {
    local url="${1}"
    if ! [[ "${url}" =~ ^https:// ]]; then
        printf "%s\n" "[err] invalid url: ${url} (must be https)"
        exit 1
    fi
}

function validate_method() {
    local method="${1}"
    if ! [[ "${method}" =~ ^(GET|POST|PUT)$ ]]; then
        printf "[err] invalid http method: ${method} (use GET, POST, or PUT)"
        exit 1
    fi
}

function validate_command() {
    local cmd="${1}"
    if ! command -v "${cmd}" &>/dev/null; then
        printf "[wrn] command '${cmd}' not found in path"
    fi
}

output_file="canaries.txt"
method="POST"
commands=()
webhook_headers=()
channel=""

while getopts "c:s:u:m:x:t:h" opts; do
    case "${opts}" in
        c) commands+=("${OPTARG}");;
        s) service="${OPTARG}";;
        u) webhook_url="${OPTARG}";;
        m) method="${OPTARG}";;
        x) webhook_headers+=("${OPTARG}");;
        t) channel="${OPTARG}";;
        h) usage;;
        *) usage;;
    esac
done

if test ${#commands[@]} -eq 0; then
    printf "%s\n" "[err] at least one command (-c) is required"
    usage
fi
if ! test "${webhook_url}" && ! test "${service}"; then
    printf "%s\n" "[err] webhook url (-u) or service (-s) is required"
    usage
fi

validate_method "${method}"
for cmd in "${commands[@]}"; do
    validate_command "${cmd}"
done

test "${webhook_url}" && validate_url "${webhook_url}"

case "${service}" in
    discord)
        webhook_url="${webhook_url:-}"
        webhook_headers+=("Content-Type: application/json")
        webhook_data="{\"username\":\"notifications-bot\", \"content\":\"**Command Canary Triggered**\nHostname: \${HOSTNAME}\nCommand: \${FUNCNAME[0]}\nTime: \$(date -u --iso-8601=seconds)\"}"
        ;;

    slack)
        webhook_headers+=("Content-Type: application/json")
        ! test "${channel}" || { printf "%s\n" "[err] slack requires a channel (-t)"; exit 1; }
        webhook_data="{\"text\":\"**Command Canary Triggered**\\nHostname: \${HOSTNAME}\\nCommand: \${FUNCNAME[0]}\\nTime: \$(date -u --iso-8601=seconds)\",\"channel\":\"${channel}\"}"
        ;;
    *)
        usage
        ;;
esac

curl_headers=""
for header in "${webhook_headers[@]}"; do
    curl_headers+="-H '${header}' "
done

>"${output_file}"

for cmd in "${commands[@]}"; do
    sanitized_data=$(printf "%s" "${webhook_data}" | sed -e 's/"/\\"/g')
    cat >> "${output_file}" << EOF
function ${cmd}() {
    (
        $(which curl) -sSkL \\
            -X ${method} \\
            ${curl_headers} \\
            --data "${sanitized_data}" \\
            ${webhook_url} \\
            -o /dev/null 2>&1 >/dev/null &
    )
    command ${cmd} "\${@}"
}
EOF
done

printf "%s\n" \
    "[inf] successfully created ${output_file}" \
    "[inf] to enable canaries, append the contents to:" \
    "  - System-wide: /etc/bash.bashrc" \
    "  - User-specific: ~/.bashrc"