wlk.sh 6.37 KB
Newer Older
UltimateByte's avatar
UltimateByte committed
1
2
#!/bin/bash
# Name: Wrong Listener Killer
UltimateByte's avatar
UltimateByte committed
3
4
# Version: 2017-05-24
# Description: Kills processes that do not meet defined rules on a given port
UltimateByte's avatar
UltimateByte committed
5
6
# Help: Default settings are to make sure that what listens to port :80 is httpd and started as root
# Developer: Robin Labadie
UltimateByte's avatar
UltimateByte committed
7
# Website: www.lrob.fr
UltimateByte's avatar
UltimateByte committed
8
9

## Settings
UltimateByte's avatar
UltimateByte committed
10
portcheck=":80" # Which port to check
UltimateByte's avatar
UltimateByte committed
11
12
13
14
allowedname="httpd" # Which process should we get on speccified port
allowedpath="/usr/sbin/httpd" # Which is the correct path to run it
allowedusers="root;" # Which is the correct user to run it (separate with ; )

UltimateByte's avatar
UltimateByte committed
15
16
preaction="service ${allowedname} restart" # Run a custom action if a problem is found
postaction="" # Run a custom action after a problem was found and processes killed
UltimateByte's avatar
UltimateByte committed
17
downaction="service ${allowedname} start"
18

UltimateByte's avatar
UltimateByte committed
19
logdir="/root" # Log directory (don't end with /)
UltimateByte's avatar
UltimateByte committed
20
21
22
mailalert="yes" # Wether to send a mail alert or not (yes/no)
mailaddress="root@localhost" # Mail to send an alert to if a threat is detected

23
sleeptime="0.1" # Sleep between kills
UltimateByte's avatar
UltimateByte committed
24
maxruns="30" # How many PID this script can kill
UltimateByte's avatar
UltimateByte committed
25
26

## Misc vars
UltimateByte's avatar
UltimateByte committed
27
selfname="wrong_listener_killer" # Name of the script and log
UltimateByte's avatar
UltimateByte committed
28
29
log="${logdir}/${selfname}.log" # Define log name

UltimateByte's avatar
UltimateByte committed
30
31
32
33
############
### Code ###
############

UltimateByte's avatar
UltimateByte committed
34
35
36
37
38
39
40
41
42

### MISC FUNCTIONS ###
# Manage logs
fn_logging(){
	# Create logfile
	if [ -n "${log}" ]&&[ ! -f "${log}" ]; then
		touch "${log}"
	fi
}
UltimateByte's avatar
UltimateByte committed
43

UltimateByte's avatar
UltimateByte committed
44
45
46
47
48
# Give a form to echo messages
fn_echo_form(){
	echoform="$(date +%Y-%m-%d_%H:%M:%S) - ${selfname} - ${currmessage}"
}

UltimateByte's avatar
UltimateByte committed
49
50
51
# Simple echo with date and selfname
# Usage fn_echo "Your Message"
fn_echo(){
UltimateByte's avatar
UltimateByte committed
52
	currmessage="$1"
UltimateByte's avatar
UltimateByte committed
53
54
	fn_echo_form
	echo -e "${echoform}"
UltimateByte's avatar
UltimateByte committed
55
56
57
58
}

# Echo with date and output to log at the same time
# Usage fn_logecho "Your Message"
UltimateByte's avatar
UltimateByte committed
59
fn_logecho(){
UltimateByte's avatar
UltimateByte committed
60
61
62
	currmessage="$1"
	fn_echo_form
	echo -e "${echoform}"
UltimateByte's avatar
UltimateByte committed
63
	echo -e "${echoform}" >> "${log}"
64
	currlog="${currlog}${echoform}\n"
UltimateByte's avatar
UltimateByte committed
65
66
}

UltimateByte's avatar
UltimateByte committed
67
68
69
70
# Send mail alert
fn_mail_alert(){
	if [ "${mailalert}" == "yes" ]; then
		fn_logecho "[INFO] Sending mail alert to: ${mailaddress}"
71
		echo -e "${currlog}" | mail -s "$(hostname -s) - ${pidname} - ${portcheck} killed" ${mailaddress}
UltimateByte's avatar
UltimateByte committed
72
73
74
75
76
77
78
79
80
	fi
	# Since this is the last action that should occur
	exit
}

### CORE FUNCTIONS ###

# Check if we can find the PID of a listening program on $portcheck
fn_define_pid(){
UltimateByte's avatar
UltimateByte committed
81
	pid="$(netstat -atunp | grep "${portcheck} " | grep LISTEN | awk '{print $7}' | awk -F "/" '{print $1}')"
UltimateByte's avatar
UltimateByte committed
82
83
	# If nothing listens unpon first start
	if [ -z "${pid}" ]&&[ -z "${actiontaken}" ]; then
UltimateByte's avatar
UltimateByte committed
84
85
		fn_logecho "[INFO] Nothing found on port ${portcheck}"
		if [ -n "${downaction}" ]; then
UltimateByte's avatar
UltimateByte committed
86
87
			fn_logecho "[ACTION] Executing command: ${downaction}"
			${downaction}
UltimateByte's avatar
UltimateByte committed
88
		fi
UltimateByte's avatar
UltimateByte committed
89
90
91
92
93
94
		exit
	# If nothing listens after getting some processes killed
	elif [ -z "${pid}" ]&&[ "${actiontaken}" == "1" ]; then
		fn_logecho "[OK] Nothing listens on port ${portcheck} anymore | Exit"
		# Send mail alert 
		fn_mail_alert
UltimateByte's avatar
UltimateByte committed
95
96
	fi
}
UltimateByte's avatar
UltimateByte committed
97

UltimateByte's avatar
UltimateByte committed
98
# Define what listens to $portcheck
UltimateByte's avatar
UltimateByte committed
99
fn_define_vars(){
UltimateByte's avatar
UltimateByte committed
100
	fn_define_pid
UltimateByte's avatar
UltimateByte committed
101
102
103
	pidname="$(netstat -atunp | grep "${portcheck} " | grep LISTEN | awk '{print $7}' | awk -F "/" '{print $2}')"
	piduser="$(ps -u -p "${pid}" | tail -1 | awk '{print $1}')"
	pidcommand="$(ps -u -p "${pid}" | tail -1 | awk '{print $11}')"
UltimateByte's avatar
UltimateByte committed
104
105
106
107
108
109
110
111
	# If one of these vars are unset, then something went wrong
	if [ -z "${pidname}" ]||[ -z "${piduser}" ]||[ -z "${pidcommand}" ]; then
		fn_logecho "[ERROR] Could not get app info with PID"
		fn_logecho "Exiting"
		exit 1
	fi
	# Provide info to the user
	fn_echo "Current program listening to ${portcheck} is : PID: ${pid}\tName: ${pidname}\tUser: ${piduser}\tPath: ${pidcommand}"
UltimateByte's avatar
UltimateByte committed
112
}
UltimateByte's avatar
UltimateByte committed
113

UltimateByte's avatar
UltimateByte committed
114
115
116
## Evaluate issues
fn_evaluate(){
	# Check process name
UltimateByte's avatar
UltimateByte committed
117
	if [ "${pidname}" != "${allowedname}" ]; then
UltimateByte's avatar
UltimateByte committed
118
		harm="1"
UltimateByte's avatar
UltimateByte committed
119
	fi
UltimateByte's avatar
UltimateByte committed
120

UltimateByte's avatar
UltimateByte committed
121
122
123
	# Check process ownership
	# We will be using a loop since multiple users might be allowed to run it
	# See how many users we have to check so that we can end the loop
UltimateByte's avatar
UltimateByte committed
124
	usersamount="$(echo "${allowedusers}" | awk -F ';' '{ print NF }')"
UltimateByte's avatar
UltimateByte committed
125
126
	allowedtorun="0" # This var is the test result and will be set to 1 if the right user is detected
	# Entering the loop to go through allowed users
UltimateByte's avatar
UltimateByte committed
127
128
129
	for ((usersindex=1; usersindex <= usersamount; usersindex++)); do
		# Put current user into a test variable
		usertest="$(echo "${allowedusers}" | awk -F ';' -v x=${usersindex} '{ print $x }')"
UltimateByte's avatar
UltimateByte committed
130
		# Test if user is allowed, register success if it is
UltimateByte's avatar
UltimateByte committed
131
132
133
134
		if [ "${piduser}" == "${usertest}" ]; then
			allowedtorun="1"
		fi
	done
UltimateByte's avatar
UltimateByte committed
135
	# Result
UltimateByte's avatar
UltimateByte committed
136
137
	if [ "${allowedtorun}" == "0" ]; then
		harm="1"
UltimateByte's avatar
UltimateByte committed
138
139
	fi

UltimateByte's avatar
UltimateByte committed
140
	# Check process path
UltimateByte's avatar
UltimateByte committed
141
142
143
	if [ "${pidcommand}" != "${allowedpath}" ]; then
		harm="1"
	fi
UltimateByte's avatar
UltimateByte committed
144
}
UltimateByte's avatar
UltimateByte committed
145

146
# Execute an action before proceeding
UltimateByte's avatar
UltimateByte committed
147
148
149
150
fn_preaction(){
	if [ -n "${preaction}" ]&&[ "${harm}" == "1" ]&&[ -z "${actiontaken}" ]; then
		fn_logecho "[ACTION] Applying pre-action: ${preaction}"
		${preaction}
UltimateByte's avatar
UltimateByte committed
151
152
153
154
155
156
		actiontaken="1"
		refresh="1"
	else
		# Misc var to tell that an action has been taken
		actiontaken="1"
		fn_logecho "[ACTION] Refreshing info ${pid}"
157
158
159
160
	fi
} 

# Execute an action after proceeding
UltimateByte's avatar
UltimateByte committed
161
162
163
164
fn_postaction(){
	if [ -n "${postaction}" ]; then
		fn_logecho "[ACTION] Applying post-action: ${postaction}"
		${postaction}
165
166
167
	fi
}

UltimateByte's avatar
UltimateByte committed
168
169
170
## Take action
fn_action(){
	## Problematic process was found
UltimateByte's avatar
UltimateByte committed
171
	if [ "${harm}" == "1" ]; then
UltimateByte's avatar
UltimateByte committed
172
173
174
		fn_logecho "[ALERT] Process on port ${portcheck} does not meet requirements"
		fn_logecho "[INFO] Expected: Name: ${allowedname}\tUser: ${allowedusers}\tPath: ${allowedpath}"
		fn_logecho "[INFO] Actual  : Name: ${pidname}\tUser: ${piduser}\tPath: ${pidcommand}"
175
		# Take the "before" action
UltimateByte's avatar
UltimateByte committed
176
		fn_preaction
UltimateByte's avatar
UltimateByte committed
177
178
179
180
181
182
		# If a before action has been done, refresh info
		if [ -n "${refresh}" ]; then
			unset refresh
			fn_define_vars
			fn_evaluate
			fn_action
UltimateByte's avatar
UltimateByte committed
183
		else
UltimateByte's avatar
UltimateByte committed
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
			# Kill the app
			fn_logecho "[ACTION] Killing PID ${pid}"
			kill -9 "${pid}"
			# Reset harm for future tests
			unset harm
			# Misc var to count how many time we ran this
			count=$((count+1))
			# If $count is greater or equel to $maxruns; then end there
			if [ "${count}" -ge "${maxruns}" ]; then
				fn_logecho "[WARNING] Exiting because the loop has reached the maximum ${maxruns} runs"
				fn_mail_alert
			# Otherwise, let's run it again
			else
				sleep "${sleeptime}"
				fn_run_functions
			fi
UltimateByte's avatar
UltimateByte committed
200
201
		fi
	elif [ "${actiontaken}" == "1" ]; then
UltimateByte's avatar
UltimateByte committed
202
		fn_logecho "[OK] The process on port ${portcheck} now meets requirements"
UltimateByte's avatar
UltimateByte committed
203
		fn_postaction
204
		fn_mail_alert
UltimateByte's avatar
UltimateByte committed
205
	else
UltimateByte's avatar
UltimateByte committed
206
		fn_logecho "[OK] The process on port ${portcheck} meets requirements"
UltimateByte's avatar
UltimateByte committed
207
		fn_postaction
UltimateByte's avatar
UltimateByte committed
208
		exit
UltimateByte's avatar
UltimateByte committed
209
210
	fi
}
UltimateByte's avatar
UltimateByte committed
211
212
213
214
215
216
217
218
219

### RUN FUNCTIONS ###
fn_logging
fn_run_functions(){
	fn_define_pid
	fn_define_vars
	fn_evaluate
	fn_action
}
UltimateByte's avatar
UltimateByte committed
220
fn_run_functions