wlk.sh 5.92 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 ; )

15
16
17
actionbefore="service ${allowedname} restart" # Run a custom action if a problem is found
actionafter="service ${allowedname} restart" # Run a custom action after a problem was found and processes killed

UltimateByte's avatar
UltimateByte committed
18
logdir="/root" # Log directory (don't end with /)
UltimateByte's avatar
UltimateByte committed
19
20
21
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

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

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

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

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

### MISC FUNCTIONS ###
# Manage logs
fn_logging(){
	# Create logfile
	if [ -n "${log}" ]&&[ ! -f "${log}" ]; then
		touch "${log}"
	fi
}
UltimateByte's avatar
UltimateByte committed
42
43
44
45
46
47
48
49
50
51

# Simple echo with date and selfname
# Usage fn_echo "Your Message"
fn_echo(){
	currmessage="$@"
	echo -e "$(date +%Y-%m-%d_%H:%M:%S) - ${selfname} - ${currmessage}"
}

# Echo with date and output to log at the same time
# Usage fn_logecho "Your Message"
UltimateByte's avatar
UltimateByte committed
52
fn_logecho(){
UltimateByte's avatar
UltimateByte committed
53
	fn_echo
UltimateByte's avatar
UltimateByte committed
54
	echo -e "$(date +%Y-%m-%d_%H:%M:%S) - ${selfname} - ${currmessage}" >> "${log}"
UltimateByte's avatar
UltimateByte committed
55
	currlog="${currlog}$(echo -e "$(date +%Y-%m-%d_%H:%M:%S) - ${selfname} - ${currmessage}")"
UltimateByte's avatar
UltimateByte committed
56
57
}

UltimateByte's avatar
UltimateByte committed
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# Send mail alert
fn_mail_alert(){
	if [ "${mailalert}" == "yes" ]; then
		fn_logecho "[INFO] Sending mail alert to: ${mailaddress}"
		echo "${currlog}" | mail -s "$(hostname -s) - ${pidname} - ${portcheck} killed" ${mailaddress}
	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
72
	pid="$(netstat -atunp | grep "${portcheck} " | grep LISTEN | awk '{print $7}' | awk -F "/" '{print $1}')"
UltimateByte's avatar
UltimateByte committed
73
74
75
76
77
78
79
80
81
	# If nothing listens unpon first start
	if [ -z "${pid}" ]&&[ -z "${actiontaken}" ]; then
		fn_logecho "[INFO] Nothing found on port ${portcheck} | Exit"
		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
82
83
	fi
}
UltimateByte's avatar
UltimateByte committed
84

UltimateByte's avatar
UltimateByte committed
85
# Define what listens to $portcheck
UltimateByte's avatar
UltimateByte committed
86
fn_define_vars(){
UltimateByte's avatar
UltimateByte committed
87
	fn_define_pid
UltimateByte's avatar
UltimateByte committed
88
89
90
	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
91
92
93
94
95
96
97
98
	# 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
99
}
UltimateByte's avatar
UltimateByte committed
100

UltimateByte's avatar
UltimateByte committed
101
102
103
## Evaluate issues
fn_evaluate(){
	# Check process name
UltimateByte's avatar
UltimateByte committed
104
	if [ "${pidname}" != "${allowedname}" ]; then
UltimateByte's avatar
UltimateByte committed
105
		harm="1"
UltimateByte's avatar
UltimateByte committed
106
	fi
UltimateByte's avatar
UltimateByte committed
107

UltimateByte's avatar
UltimateByte committed
108
109
110
	# 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
111
	usersamount="$(echo "${allowedusers}" | awk -F ';' '{ print NF }')"
UltimateByte's avatar
UltimateByte committed
112
113
	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
114
115
116
	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
117
		# Test if user is allowed, register success if it is
UltimateByte's avatar
UltimateByte committed
118
119
120
121
		if [ "${piduser}" == "${usertest}" ]; then
			allowedtorun="1"
		fi
	done
UltimateByte's avatar
UltimateByte committed
122
	# Result
UltimateByte's avatar
UltimateByte committed
123
124
	if [ "${allowedtorun}" == "0" ]; then
		harm="1"
UltimateByte's avatar
UltimateByte committed
125
126
	fi

UltimateByte's avatar
UltimateByte committed
127
	# Check process path
UltimateByte's avatar
UltimateByte committed
128
129
130
	if [ "${pidcommand}" != "${allowedpath}" ]; then
		harm="1"
	fi
UltimateByte's avatar
UltimateByte committed
131
}
UltimateByte's avatar
UltimateByte committed
132

133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# Execute an action before proceeding
fn_actionbefore(){
	if [ -n "${actionbefore}" ]; then
		fn_logecho "[ACTION] Applying actionbefore: ${actionbefore}"
		${actionbefore}
	fi
} 

# Execute an action after proceeding
fn_actionafter(){
	if [ -n "${actionafter}" ]; then
		fn_logecho "[ACTION] Applying actionafter: ${actionafter}"
		${actionafter}
	fi
}

UltimateByte's avatar
UltimateByte committed
149
150
151
## Take action
fn_action(){
	## Problematic process was found
UltimateByte's avatar
UltimateByte committed
152
	if [ "${harm}" == "1" ]; then
UltimateByte's avatar
UltimateByte committed
153
154
155
		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}"
156
157
158
		# Take the "before" action
		fn_actionbefore
		# Kill the app
UltimateByte's avatar
UltimateByte committed
159
		fn_logecho "[ACTION] Killing PID ${pid}"
UltimateByte's avatar
UltimateByte committed
160
		kill -9 "${pid}"
UltimateByte's avatar
UltimateByte committed
161
162
163
164
165
		# Reset harm for future tests
		unset harm
		# Misc var to tell that an action has been taken
		actiontaken="1"
		# Misc var to count how many time we ran this
UltimateByte's avatar
UltimateByte committed
166
		count=$((count+1))
UltimateByte's avatar
UltimateByte committed
167
168
169
170
171
172
		# 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
UltimateByte's avatar
UltimateByte committed
173
			sleep "${sleeptime}"
UltimateByte's avatar
UltimateByte committed
174
175
176
			fn_run_functions
		fi
	elif [ "${actiontaken}" == "1" ]; then
UltimateByte's avatar
UltimateByte committed
177
		fn_logecho "[OK] The process on port ${portcheck} now meets requirements"
178
		fn_mail_alert
UltimateByte's avatar
UltimateByte committed
179
	else
UltimateByte's avatar
UltimateByte committed
180
		fn_logecho "[OK] The process on port ${portcheck} meets requirements"
UltimateByte's avatar
UltimateByte committed
181
		exit
UltimateByte's avatar
UltimateByte committed
182
183
	fi
}
UltimateByte's avatar
UltimateByte committed
184
185
186
187
188
189
190
191
192

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