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"
|