diff options
| -rw-r--r-- | setup-command-canaries.sh | 138 | 
1 files changed, 138 insertions, 0 deletions
| diff --git a/setup-command-canaries.sh b/setup-command-canaries.sh new file mode 100644 index 0000000..60324bd --- /dev/null +++ b/setup-command-canaries.sh @@ -0,0 +1,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" |