I'm designing a web interface to a controller for my school's bell. Are there any security flaws in this code?
The source code is available here.
Front End:
bell-ringer.cgi
:
#!/bin/bash
function unescape
{
local s="$1"
while [ "a$s" != "a" ]; do
if [ "a${s:0:1}" == "a%" -a "a`echo "${s:1:2}" | sed '{s/\([0-9a-fA-F]\)/\1/gp; s/.*//}'`" == "a${s:1:2}" ]; then
printf "\x${s:1:2}"
s="${s:3}"
else
printf "%s" "${s:0:1}"
s="${s:1}"
fi
done
}
function escape
{
local s="$1"
while [ "a$s" != "a" ]; do
if [ "a`echo "${s:0:1}" | grep "[a-zA-Z0-9 ]"`" != "a" ]; then
printf "%s" "${s:0:1}"
else
printf "%s" "${s:0:1}" | hexdump -e '/1 "&#%02X;"'
fi
s="${s:1}"
done
}
function output_content
{
cat <<EOF
Content-Type: text/html; charset=UTF-8
<!DOCTYPE html>
<html>
<head>
<title>Administration Building Bell Ringer</title>
</head>
<body>
<div style="margin-top: 10%; margin-left: 30%; margin-right: 30%; background-color: #E5E2E0; text-align: center">
<span>Main Page | <a href="change-password.cgi">Set Password</a></span>
<h2>Bell Ringer</h2><br/>
${status_message}<p>The Bell Ringer is currently $(if [ "a$(ps -A | sed '{s/bell-ringer\.cgi//}' | grep "bell-ringer")" != "a" ]; then echo "running"; else echo "not running"; fi)</p><br/>
<form action="bell-ringer.cgi" method="POST">
Action:<br/>
<select name="action">
<option value="" selected="">--</option>
<option value="start">Start Bell Ringer</option>
<option value="stop">Stop Bell Ringer</option>
</select><br/>
Ring Count (for starting): <input type="number" name="ring_count" value="0"/><br/>
Username: <input type="text" name="username" value="$username"/><br/>
Password: <input type="password" name="password"></input><br/>
<input type="submit" value="Ok"/>
</form>
</div>
</body>
EOF
}
if [ "${HTTPS:=off}" == "off" ]; then
printf "Location: https://%s%s\n\n" "${HTTP_HOST}" "${REQUEST_URI}"
exit 0
fi
if [ "${REQUEST_METHOD:=GET}" == "GET" ]; then
output_content
exit 0
fi
sed '{s/\([-_a-z0-9A-Z]*\)=\([^&=]*\)&*/\1=\2\n/gp; s/.*//}' | grep "..*" | (
while read input_line; do
var_name="`echo "$input_line" | sed '{s/\([^=]*\)=.*/\1/p; s/.*//}' | grep "..*"`"
if [ "a$var_name" != "a" ]; then
var_value="$(unescape "`echo "$input_line" | sed '{s/[^=]*=\(.*\)/\1/}'`")"
if [ "a$var_name" == "ausername" ]; then
username="$var_value"
elif [ "a$var_name" == "apassword" ]; then
password="$var_value"
elif [ "a$var_name" == "aaction" ]; then
action="$var_value"
elif [ "a$var_name" == "aring_count" ]; then
ring_count="$var_value"
fi
fi
done
if [ "a$ring_count" == "a0" ]; then
ring_count=""
fi
status_message="`printf "%s\n" "$password" | (bell-ringer-administrate bell "$username" "$action" "$ring_count" 2>&1) | (while read s; do printf "%s<br/>" "$s"; done)`"
if [ "a$status_message" != "a" ]; then
status_message="<p>$status_message</p><br/>"
fi
sleep 0.5
output_content
)
change-password.cgi
:
#!/bin/bash
function unescape
{
local s="$1"
while [ "a$s" != "a" ]; do
if [ "a${s:0:1}" == "a%" -a "a`echo "${s:1:2}" | sed '{s/\([0-9a-fA-F]\)/\1/gp; s/.*//}'`" == "a${s:1:2}" ]; then
printf "\x${s:1:2}"
s="${s:3}"
else
printf "%s" "${s:0:1}"
s="${s:1}"
fi
done
}
function escape
{
local s="$1"
while [ "a$s" != "a" ]; do
if [ "a`echo "${s:0:1}" | grep "[a-zA-Z0-9 ]"`" != "a" ]; then
printf "%s" "${s:0:1}"
else
printf "%s" "${s:0:1}" | hexdump -e '/1 "&#%02X;"'
fi
s="${s:1}"
done
}
function output_content
{
cat <<EOF
Content-Type: text/html; charset=UTF-8
<!DOCTYPE html>
<html>
<head>
<title>Administration Building Bell Ringer - Change Password</title>
</head>
<body>
<div style="margin-top: 10%; margin-left: 30%; margin-right: 30%; background-color: #E5E2E0; text-align: center">
<span><a href="bell-ringer.cgi">Main Page</a> | Set Password</span>
<h2>Bell Ringer - Change Password</h2><br/>
${status_message}
<form action="change-password.cgi" method="POST">
Username: <input type="text" name="username" value="$username"/><br/>
Old Password: <input type="password" name="password"></input><br/>
New Password: <input type="password" name="password1"></input><br/>
Reenter New Password: <input type="password" name="password2"></input><br/>
<input type="submit" value="Change Password"/>
</form>
</div>
</body>
EOF
}
if [ "${HTTPS:=off}" == "off" ]; then
printf "Location: https://%s%s\n\n" "${HTTP_HOST}" "${REQUEST_URI}"
exit 0
fi
if [ "${REQUEST_METHOD:=GET}" == "GET" ]; then
output_content
exit 0
fi
sed '{s/\([-_a-z0-9A-Z]*\)=\([^&=]*\)&*/\1=\2\n/gp; s/.*//}' | grep "..*" | (
while read input_line; do
var_name="`echo "$input_line" | sed '{s/\([^=]*\)=.*/\1/p; s/.*//}' | grep "..*"`"
if [ "a$var_name" != "a" ]; then
var_value="$(unescape "`echo "$input_line" | sed '{s/[^=]*=\(.*\)/\1/}'`")"
if [ "a$var_name" == "ausername" ]; then
username="$var_value"
elif [ "a$var_name" == "apassword" ]; then
password="$var_value"
elif [ "a$var_name" == "apassword1" ]; then
password1="$var_value"
elif [ "a$var_name" == "apassword2" ]; then
password2="$var_value"
fi
fi
done
status_message="`printf "%s\n" "$password" "$password1" "$password2" | (bell-ringer-administrate passwd "$username" 2>&1) | (while read s; do printf "%s<br/>" "$s"; done)`"
if [ "a$status_message" != "a" ]; then
status_message="<p>$status_message</p><br/>"
fi
sleep 0.5
output_content
)
Back End:
set-uid root bell-ringer-administrate
:
#include <unistd.h>
int main(int argc, char **argv, char *const *envp)
{
setuid(0);
setgid(0);
execve("/usr/local/bin/bell-ringer-administrate.sh", argv, envp);
return 1;
}
bell-ringer-administrate.sh
:
#!/bin/bash
function get_users_in_group
{
local group_name="$1"
grep "^$group_name:" < /etc/group | sed '{s/[^:]*:[^:]*:[^:]*:\([^:]*\)/\1/p; s/.*//}' | sed '{s/,/ /g}' | grep "."
}
if [ "a`whoami`" != "aroot" ]; then
echo "must be run as root" 1>&2
exit 1
fi
valid_users="`get_users_in_group "adm"`"
found=0
if [ "a$1" != "abell" -a "a$1" != "apasswd" ]; then
echo "invalid action class" 1>&2
exit 1
fi
action_class="$1"
for i in $valid_users; do
if [ "a$i" == "a$2" -a "a$2" != "a" ]; then
found=1
fi
done
if [ $found == 0 ]; then
echo "invalid user name or password" 1>&2
exit 1
fi
if [ $action_class == bell ]; then
if [ "a$3" != "astart" -a "a$3" != "astop" ]; then
echo "invalid action" 1>&2
exit 1
fi
arg=""
if [ "a$4" != "a" -a "$3" == "start" ]; then
arg="$(($4))"
fi
su "$2" -c "sudo -k; sudo -S -- bash -c \"(${3}-bell-ringer.sh $arg 2>&1); exit 0\"" 2> /dev/null || echo "invalid user name or password" 1>&2
elif [ $action_class == passwd ]; then
read old_password
read new_password
read new_password2
invalid_character_set=':'
if [ "a$new_password" == "a" ]; then
echo "empty password" 1>&2
exit 1
elif [ "a`printf "%s\n" "$new_password" | grep "[$invalid_character_set]"`" != "a" ]; then
echo "invalid character in password. invalid characters '$invalid_character_set'" 1>&2
exit 1
elif [ "a$new_password" != "a$new_password2" ]; then
echo "new passwords don't match" 1>&2
exit 1
else
printf "%s\n" "$old_password" "$2:$new_password" | su "$2" -c "sudo -k; sudo -S -- bash -c \"(chpasswd 2>&1 && echo password changed successfully); exit 0\"" 2> /dev/null || echo "invalid user name or password" 1>&2
fi
else
echo "invalid action class 2" 1>&2
exit 1
fi
Daemon control:
start-bell-ringer.sh
:
#!/bin/bash
if [ "a`whoami`" != "aroot" ]; then
echo "must be root" 1>&2
exit 1
fi
killall bell-ringer >& /dev/null
if [ "a$1" == "a" ]; then
su bell-ringer -c "(bell-ringer -v &>> /home/bell-ringer/bell-ringer.log&)" 2> /dev/null
echo "started bell ringer"
else
su bell-ringer -c "(bell-ringer -vr $(($1)) &>> /home/bell-ringer/bell-ringer.log&)" 2> /dev/null
echo "started bell ringer ringing $(($1)) times"
fi
stop-bell-ringer.sh
:
#!/bin/bash
if [ "a`whoami`" != "aroot" ]; then
echo "must be root" 1>&2
exit 1
fi
killall bell-ringer >& /dev/null
echo "stopped bell ringer"