Controlling the World from a Web Browser
A presentation on how to use the Raspberry Pi (and a web browser) to control the world (or just about anything in it).
9:30 am
Victoria Computer Club
85A Burnside Rd West (at Wascana), Victoria, BC (map)
Control from the web
I have shamelessly borrowed this from one of my Camosun labs.
Objectives
- Control the Pi from the Web
Example
Concepts
-
- Web Forms https://en.wikipedia.org/wiki/Form_%28HTML%29
- Web forms allow us to enter values to be passed to a server
and from there to a program that the web server
executes. See cgi-bin below. - This ability to collect input is crucial to much that we do
on the web. In fact most of what we see today of the
dynamic web, cloud services, on line collaboration etc. would,
obviously, not work if we could not input data.
Unfortunately there is a down side. As humans we make
mistakes, and sometimes we “make mistakes” on purpose in order
to break, compromise or crack something. Like your web
server. - Validation
So, as soon as we start writing programs that accept input
from the “Web” we need to ensure that the input we get is what
we expect. And, if not we need to not use the input and
either complain or ignore it.Given the “Sand Box” environment we are working in, the danger
is not too high that someone malicious will try to crack our
cgi-bin scripts, but we need to get into the habit of always
sanitizing our inputs. Besides, we, as unlikely as this
may seem, might make a misteak.
- Web forms allow us to enter values to be passed to a server
- Web Servers https://en.wikipedia.org/wiki/Apache_HTTP_Server
- The web server is the server side partner to your web
browser. When a web page is requested the server finds
the web page on its system, probably disk, and sends it
back to the requesting browser. - Directories of interest
- /var/www – contains .html files to be served.
- /usr/lib/cgi-bin – contains programs to be executed.
- The web server is the server side partner to your web
- Common Gateway Interface (CGI) https://en.wikipedia.org/wiki/Common_Gateway_Interface
- The CGI interface is a way to tell the web server that we
want it to do more than grab the file from disk and send it to
us. We want it to run the file as a program and send the
results of that program running to us. The program that
runs can be written in any language we choose. The only
rules are:- That the program is in the “cgi-bin” directory. On
our Pi this is /usr/lib/cgi-bin/ - That when the program runs it sends the appropriate header
telling the web server and browser what the content type
that it is creating is. This is a line that looks
like:Content-Type:text/html\n\n
- Assuming your output is html text.
- The content type could be text/plain or image/png etc.
- The \n\n is two new line characters, thus creating a
blank line. The blank line is the indication to the
Server/Browser that the HTTP is done and the (HTML)
content will now start. You would create this header
in Python withprint
“Content-Type: text/html\n\n”
- That the program is in the “cgi-bin” directory. On
- The CGI interface is a way to tell the web server that we
- Python – collecting form variables
- Form variables can be GET or POST. We will use
POST. Post gets to the server via standard in (stdin). - Stdin https://en.wikipedia.org/wiki/Standard_streams
- A running program in Linux has stdin, stdout and stderr.
- In the browser/server system stdin is input from the
network, stdout is output to the network and stderr is
output to the network. - The following is Python code that will pick up the post
variables that the web browser form submits and put them
into the dictionary POST{}:
- Form variables can be GET or POST. We will use
- Web Forms https://en.wikipedia.org/wiki/Form_%28HTML%29
# Build dictionary of post inputs.
import sys
POST = {}
# Split each input parameter on ampersand.
args = sys.stdin.read().split('&')
for arg in args:
# split the name value pairs and add
them to the POST dictionary
inp = arg.split('=')
POST[inp[0]] = inp[1]
-
-
- Check validity https://en.wikipedia.org/wiki/Data_validationFor the On/Off form:
- Input should be on or off. Anything else, including
nothing, should be ignored or an error. - We can do this with a series of if/elif/else statements.
- Input should be on or off. Anything else, including
- Check validity https://en.wikipedia.org/wiki/Data_validationFor the On/Off form:
- Executing as Root – Set UID https://en.wikipedia.org/wiki/Setuid
- We have two problems if we want to use the GPIO functions
from a cgi-bin script:- GPIO functions must run as root. We normally do that
by running the programs like “sudo ./myprog.py” and this
works. But from the web – not so much.- We could give the Apache2 user (www-data) sudo
privileges. But, do we really want to allow the
Apache user and thus anyone connecting via the web to our
machine, complete access to run anything as root.
This means that if we open our Pi server to to outside
world then that same outside world could run stuff as
root. Not a good thing. - Normally when we run a program or a script, it runs with
the privileges of the person who runs it, not the
privileges of the owner.We can set our script to what is called suid. And
when the script is run it will run with the privileges of
the owner of the file, not the “runner”. An
example is the Linux passwd command. This allows a
plain user to change a value in a root read only
file. That value is your password.-rwsr-xr-x. 1
root root 30768 Feb 22 2012 /usr/bin/passwdAnd, the passwd program is very carefully written to
ensure that you may only change your password. Not
someone else’s. - But there is another problem. Scripts can have
security issues in Linux in the way they are run, see next
point. So in general Linux will refuse to run any
script in setuid mode. In order to solve this
problem we can use a compiled C wrapper. The
compiled wrapper in turn runs the script.
- We could give the Apache2 user (www-data) sudo
- GPIO functions must run as root. We normally do that
- C Wrapper
-
- Why – security issues. From the Perl Security man
page (man perlsec)
Beyond the obvious problems that stem from giving special
privileges to
systems as
flexible as scripts, on many versions of Unix, set-id
scripts are
inherently insecure right from the start. The
problem is a
race condition in
the kernel. Between the time the kernel opens
the
file to see which
interpreter to run and when the (now-set-id)
interpreter turns
around and reopens the file to interpret it, the file
in question may
have changed, especially if you have symbolic links on
your system.
- How – Build a C Wrapper
- The C wrapper is compiled so it does not have the
security issues that a plain script has. Still it
is running a plain script and we must ensure that the
script is secure, inputs are checked and that it can
only do what we intended it to do. The C wrapper: #define REAL_PATH “/path/to/script”
main(ac, av)
char **av;
{
execv(REAL_PATH, av);
}
- The C wrapper is compiled so it does not have the
-
- ac is the number of arguments passed to the C program
and av is a pointer to an array of arguments. The O/S
handles setting this up so that you have the arguments
available that were passed to the script. - The first line is not I repeat NOT a
comment. This is C, not Python. It is a
declaration of a constant. You need to change this to
the fully qualified path and name of the script you want
to run, and this is the only thing you need to change in
this program. E.g. /usr/lib/cgi-bin/myscript.py - This is C so you need to compile it. gccmywrapper.c -o mywrapperwill compile the C
source file mywrapper.c into the executable file
mywrapper. Feel free to change the names to names
that make sense in this context. In fact –
please do that.By convention C executables on Linux systems do not have
an extension.Don’t mess the naming up. The compiler will happily
compile your source file to an executable of the same
name E.g. gcc -o foo.c foo.c will overwrite your source
with an executable incorrectly named. - Both the compiled C program and the script need to be
executable and the script needs a Shebang. - Change the owner of the compiled C program to root
before you use chmod to set the setuid bit.
- ac is the number of arguments passed to the C program
-
-
- chown
root:root mywrapper - chmod
4755 mywrapper
- chown
-
- Why – security issues. From the Perl Security man
-
- Log Files of interest:
- /var/log/apache2/error_log – Error logs.
- /var/log/apache2/access_log – Logs of pages served.
- .htaccess If time permits – probably next time.
- You may be tempted to open up your Pi to the World.
Don’t! Not yet.- Security issues to opening Pi to the world.
-
- ssh
- password
- sudo
- iptables
- vnc
-
- Security issues to opening Pi to the world.
- We have two problems if we want to use the GPIO functions
-
Code
Web form – this sends the request, on or off
<html>
<head>
<title>Turn something on or off</title>
</head>
<body>
<h1>Turn Something On and Off</h1>
<form action=”/cgi-bin/onoff” method=”post”>
ON: <input name=”onoff” value=”On” type=”radio”
><br>
OFF: <input name=”onoff” value=”Off” type=”radio”
><br>
<input type=”submit”>
</form>
</body>
</html>
Wrapper – this allows the suid cgi script to be executed securely
#define REAL_PATH “/usr/lib/cgi-bin/onoff.py”
main(ac, av)
char **av;
{
execv(REAL_PATH, av);
}
python back end – this does the work
#! /usr/bin/python
# Import the Python gpio library and the system library
import RPi.GPIO as GPIO
import sys
print “Content-type: text/html\n\n”
print “<html><head><title>This is a
switch</title></head”
# Build dictionary of post inputs.
POST = {}
args = sys.stdin.read().split(‘&’)
for arg in args:
t = arg.split(‘=’)
POST[t[0]] = t[1]
# Get the input value from the POST[] array
inp = POST[‘onoff’]
# Determine on or off and which message to display.
if inp == “On”:
print “<h1>Turned On</h1>”
onOff = 1
elif inp == “Off”:
print “<h1>Turned Off</h1>”
onOff = 0
else:
print “How do I do that?”
# Turn off warnings
GPIO.setwarnings(False)
# This is the gpio pin I have the led connected to
pin = 5
# Set the pin number mode to Broadcom rather than connector
order
GPIO.setmode(GPIO.BCM)
# Set the choosen pin to be an output and set the ouput true, on.
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, onOff)
# Display the state of the pin.
print GPIO.input(pin);
References
- Wikipediabook for this week
- www.tuxation.com/setuid-on-shell-scripts.html
- man persec search / security bugs
Quiz
- What is the purpose of web forms?
- How important are web forms to our experience of the web?
- Discuss why and how to validate input data.
- What is a web server and which web server are we using on the
Pi? - /var/www and /usr/lib/cgi-bin are
directories on the Pi. What are each of the
directories used for? - What is the purpose of the CGI?
- What http line do we have to output before we output html?
- Where do we get the submitted POST variables from?
- What is set uid? Why do we use it.
- What do we have be extremely careful with when we create suid
programs? - What is the purpose of the C wrapper?
- Why do we have to compile the C wrapper program?
- List two Apache log files, the directories they will be found
in and what will be found in them. - Why do we care about the log files?
- Opening up the Pi to the outside world. Why should we
not do this yet? - What is the purpose of .htaccess?
- What files do we need to create to make .htaccess work and
what file do we need to modify?