6.858 Fall 2012 Lab 2: Privilege separation

Handed out: Wednesday, September 19, 2012
Part 1 due: Friday, September 28, 2012 (5:00pm)
All parts due: Friday, October 5, 2012 (5:00pm)

Introduction

This lab will introduce you to privilege separation, in the context of a simple python web application called zoobar, where users transfer "zoobars" (credits) between each other. To help you privilege-separate this application, the zookws web server used in the previous lab is a clone of the OKWS web server, discussed in lecture 3. In this lab, you will set up a privilege separated web server, examine possible vulnerabilities, and break up the application code into less-privileged components to minimize the effects of any single vulnerability.

To fetch the new source code, use Git to commit your Lab 1 solutions, and merge them into our lab2 branch:

httpd@vm-6858:~$ cd lab 
httpd@vm-6858:~/lab$ git add answers.txt exploit-*.py [and any other new files...]
httpd@vm-6858:~/lab$ git commit -am 'my solution to lab1' 
[lab1 c54dd4d] my solution to lab1
 1 files changed, 1 insertions(+), 0 deletions(-)
httpd@vm-6858:~/lab$ git pull 
...
httpd@vm-6858:~/lab$ git checkout -b lab2 origin/lab2 
Branch lab2 set up to track remote branch lab2 from origin.
Switched to a new branch 'lab2'
httpd@vm-6858:~/lab$ git merge lab1
Merge made by recursive.
...
httpd@vm-6858:~/lab$

In some cases, Git may not be able to figure out how to merge your changes with the new lab assignment (e.g. if you modified some of the code that is changed in the second lab assignment). In that case, the git merge command will tell you which files are conflicted, and you should first resolve the conflict (by editing the relevant files) and then commit the resulting files with git commit -a.

Once your source code is in place, make sure that you can compile and install the web server and the zoobar application:

httpd@vm-6858:~/lab$ make
cc -m32 -g -std=c99 -fno-stack-protector -Wall -Werror -D_GNU_SOURCE   -c -o zookld.o zookld.c
cc -m32 -g -std=c99 -fno-stack-protector -Wall -Werror -D_GNU_SOURCE   -c -o http.o http.c
cc -m32  zookld.o http.o  -lcrypto -o zookld
cc -m32 -g -std=c99 -fno-stack-protector -Wall -Werror -D_GNU_SOURCE   -c -o zookfs.o zookfs.c
cc -m32  zookfs.o http.o  -lcrypto -o zookfs
cc -m32 -g -std=c99 -fno-stack-protector -Wall -Werror -D_GNU_SOURCE   -c -o zookd.o zookd.c
cc -m32  zookd.o http.o  -lcrypto -o zookd
cc -m32 -g -std=c99 -fno-stack-protector -Wall -Werror -D_GNU_SOURCE   -c -o zooksvc.o zooksvc.c
cc -m32  zooksvc.o  -lcrypto -o zooksvc
httpd@vm-6858:~/lab$ sudo make setup
[sudo] password for httpd: 6858
./chroot-setup.sh
+ grep -qv uid=0
+ id
...
+ python /jail/zoobar/zoodb.py init-person
+ python /jail/zoobar/zoodb.py init-transfer
httpd@vm-6858:~/lab$ 

The web server for this lab uses the /jail directory to setup chroot jails for different parts of the web server, much as in the OKWS paper. The make command compiles the web server, and make setup installs it with all the necessary permissions in the /jail directory.

As part of this lab, you will need to change how the files and directories are installed, such as changing their owner or permissions. To do this, you should not change the permissions directly. Instead, you should edit the chroot-setup.sh script in the lab directory, and re-run sudo make setup.

Now, make sure you can run the web server, and access the web site from your browser, as follows:

httpd@@vm-6858:~/lab$ /sbin/ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 00:0c:29:57:90:a1  
          inet addr:172.16.91.143  Bcast:172.16.91.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fe57:90a1/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:149 errors:0 dropped:0 overruns:0 frame:0
          TX packets:94 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:15235 (15.2 KB)  TX bytes:12801 (12.8 KB)
          Interrupt:19 Base address:0x2000 

httpd@vm-6858:~/lab$ sudo ./zookld

In this particular example, you would want to open your browser and go to http://172.16.91.143:8080/zoobar/index.cgi, or, if you are using KVM, to http://localhost:8080/zoobar/index.cgi. You should see the zoobar web site. Play around with this web application to get a feel for what it allows users to do. In short, a registered user can update his/her profile, transfer "zoobars" (credits) to another user, and look up the zoobar balance, profile, and transactions of other users in the system.

If something doesn't seem to be working, try to figure out what went wrong, or contact the course staff, before proceeding further.

Part 1: Web Server Setup

In the first part of this lab assignment, you will read the provided source code of the zookws web server and further secure it using privilege separation.

As introduced in Lab 1, the zookws web server is modeled after OKWS from lecture 4. Similar to OKWS, zookws consists of a launcher daemon zookld that launches services configured in the file zook.conf, a dispatcher zookd that routes requests to corresponding services, as well as several services. For simplicity zookws does not implement helper or logger daemon as OKWS does.

By default zookws configures only one HTTP service, zookfs_svc, that serves static files and executes dynamic scripts. The zookfs_svc does so by invoking the executable zookfs, which is jailed in the directory /jail by chroot. You can look into /jail; it contains executables (except for zookld), supporting libraries, and the zoobar web site. See zook.conf and zookfs.c for details.

The launcher daemon zookld is running under root and can bind to a privileged port like 80. Note that in the default configuration, zookd and vulnerable services are inappropriately running under root; an attacker can exploit buffer overflows and cause damages to the server, e.g., unlink a specific file as you have done in Lab 1.

To fix the problem, you should run these services under unprivileged users rather than root.

Exercise 1. Modify the configuration file zook.conf to run zookd and other services under unprivileged user IDs. Make sure that the attacker cannot read or write to sensitive files in the server, even if zookd and these services contain buffer overflow vulnerabilities. At the same time, ensure that the zoobar application continues to work: that new users can register, transfer credits, and so on.

You will need to pick additional UIDs and GIDs, set them for each service in zook.conf, and edit the chroot-setup.sh script as necessary. Remember to re-run sudo make setup after changing scripts that set up /jail if you want your changes to take effect. Keep in mind that the chroot-setup.sh script will be re-run each time by make setup and make check, so any commands that you add to these scripts should be safe to run multiple times.

Run sudo make check to verify that your modified configuration passes our basic tests (although keep in mind that our tests are not exhaustive).

Now that none of the services are running as root, we will try to further privilege-separate the zookfs_svc service that handles both static files and dynamic scripts. Although it runs under an unprivileged user, some python scripts could easily have security holes; a vulnerable python script could be tricked into deleting important static files that the server is serving. A better organization is to split zookfs_svc into two services, one for static files and the other for python scripts, running under different users.

Additionally, a client should only be able to run the intended python scripts in the /zoobar directory from a browser, namely the /zoobar/index.cgi script. For example, a client should not be able to run /password.cgi, which reveals the root password of the server. Similarly, a client should not be able to directly fetch the database files person.db and transfer.db via HTTP.

Exercise 2. Create two new HTTP services, along the lines of the existing zookfs_svc service, such that one will only execute dynamic content, and one which will only serve static files. Modify the configuration file zook.conf to split the zookfs_svc service into two services running under different users: the static service that only serves static files, and the dynamic service that only executes intended python scripts in the zoobar directory.

You may use url filtering provided in zook.conf, which supports regular expressions. For example, url = .* matches all requests, while url = /zoobar/(abc|def)\.html only allows requests to /zoobar/abc.html and /zoobar/def.html. You may also find it helpful to modify the server to not serve paths containing .., although it is not the only solution.

Run sudo make check to verify that your modified configuration passes our tests.

Now that we have privilege-separated the handling of static and dynamic content in the web server, we will look at fixing some bugs in the zoobar web application code. One of the key features of the zoobar application is the ability to transfer credits between users. This feature is implemented by the script transfer.py. Unfortunately, transfer.py has some logical bugs that may result in wrong transfers.

Exercise 3. Fix as many logical bugs as you can find in transfer.py (don't worry about browser-side attacks such as XSS for now) and note them in answers.txt. Think carefully about what kinds of inputs an attacker might provide. In our solution, there are three vulnerabilities.

Deliverables

Explain in answers.txt your changes to the zookws source code, configuration, and setup scripts for each exercise. Feel free to include any comments about your solutions in the answers.txt file (we would appreciate any feedback you may have on this assignment).

Submit your answers to the first part of the lab assignment by running make submit to upload lab2-handin.tar.gz to the submission web site.

Part 2: The zoobar Web Site

In the rest of this lab assignment, you will further secure the zoobar web site using privilege separation.

In the previous exercise, we fixed the bugs in the transfer code; now we would like to make sure we can deal with any future such bugs that come up. To do so, we want to make sure that we have a reliable log of all zoobar transfers that happened in the system. The current design stores the transfer log in the transfer SQL table, stored in zoobar/db/transfer/transfer.db. This table is accessible to all python code in the zoobar site, which means that an attacker might be able to change the history so that we will never find out about his or her attack.

We will try to make the transfer log more reliable by performing the logging operations in a separate process, running as a different user from the rest of the zoobar code. This user ID will only run logging code, which will insert new log entries into the transfer table. By setting permissions on the zoobar/db/transfer directory accordingly, we will ensure that only the logging code (which will hopefully be trustworthy) will be able to modify log entries, but any other python code will be able to read the log.

To break off some python code into a separate process, running as a separate user ID, we have provided you with some helper tools. The zooksvc service creates a Unix domain socket, and when someone connects to this socket, it will launch an arbitrary program. We have created a simple echo service using this tool. Look at how zook.conf spawns this echo service, the source code for the echo service tool in zoobar/svc-echo.py, and the sample client of this service in zoobar/demo-client.py. The client uses a simple library for connecting to Unix domain socket services, in zoobar/unixclient.py. Note that the client is meant to be invoked from the command line, rather than being executed as a CGI script via HTTP.

You may find zoobar/demo-client.py helpful in debugging any new Unix domain socket services you create.

Exercise 4. Create a new service to perform transfer logging as a separate user ID. You will need to create a new service along the lines of svc-echo.py; modify zook.conf to start it appropriately (under a different UID); modify the permissions on the transfer database directory such that only this new service can modify it; and modify the transfer.py code to invoke this service to log transactions, instead of logging transactions directly.

Make all of your changes in the lab directory rather than in /jail. In particular, if you need to set certain permissions on files or directories, or install additional files or directories in /jail, do so in the chroot-setup.sh script.

Note: be careful when picking a format for messages in your service. What if someone tries to passes spaces or a newline as an argument? (Hint: use some existing encoding like JSON. But don't use Python's pickle module.)

Run sudo make check to verify that your privilege-separated transfer service passes our tests.

Now, you will break up the zoobar code into two additional protection domains. First, we want to make sure that only the transfer code is actually able to modify the zoobar balances of different users, so that a vulnerability in the rest of the python code will not be able to directly modify the number of zoobars that some user has.

One complication in doing this rests in the fact that the zoobar balance information is stored in the same database table, person, that stores profile and login information that the rest of the code must be able to modify. To protect zoobar balances from being corrupted by the rest of the python code, you will need to create a new database table holding just the zoobar balances for each user, and remove the balance information from the person table.

Exercise 5. In preparation for privilege-separating the transfer code, split the zoobars field from the person table into a new zoobars table stored in the database file zoobar/db/zoobars/zoobars.db, and remove the zoobars column from the person table. Change the rest of the python code to access the correct table when fetching zoobar balances. Don't forget to handle the case of account creation, when the new user needs to get an initial 10 zoobars.

Exercise 6. Create a new service to transfer zoobars from one user to another. Change the transfer.py code to invoke this service instead of modifying the zoobar balances directly. Set the permissions on the new balances table such that only the transfer code can modify it, and the rest of the python code can only read it. Don't forget to handle account creation, which needs to involve your new transfer service.

Finally, make sure that only the transfer code is able to invoke the logging service -- after all, no other python code should be able to generate log entries! You should be able to do this by using groups and group permissions on the directory containing the logging service socket. As before, make sure all of your changes are reflected in the chroot-setup.sh script, and not only in the /jail directory in your VM.

Now our web application should be more secure, because compromises of most of the python code will not allow the attacker to modify zoobar balances. Unfortunately, the attacker can still subvert the web site by modifying user passwords or HTTP cookies in the person database table. For the final part of this assignment, you will move the authentication and cookie-verification code into a separate service that runs under a distinct user ID, to prevent such attacks.

Exercise 7. Split the authentication information (password, salt, and token fields) into an auth table that is separate from the original person table. Store this table in the database file zoobar/db/auth/auth.db. After you do this, the only remaining fields in the person table should be the username and the profile.

Create a new service that implements user login and cookie verification using this table. This service should implement three functions, which correspond to existing functions implemented in auth.py that you will need to replace. First, check the username and password for login, returning an HTTP cookie token if the password is correct. Second, verify whether a token is correct, returning true or false. Third, register a new user, again returning true or false depending on success.

Make sure that the auth table storing passwords and tokens is only readable by your new authentication service.

Now that the attacker cannot obtain anyone's passwords or HTTP cookies from the database, there is one last problem to fix.

Exercise 8. In the current design, the attacker can still invoke the transfer service and ask for credits to be transferred between an arbitrary pair of users. Modify the transfer service protocol to require a valid token from the sender of credits, and modify the transfer service implementation to validate this token with the authentication service before approving the transfer.

Although make check does not include an explicit test for this exercise, you should be able to check whether this feature is working or not by manually connecting to your transfer service using zoobar/demo-client.py, and verifying that it is not possible to perform a transfer without supplying a valid token.

Deliverables

Again, explain in answers.txt any non-obvious changes you made to zookws and zoobar for each exercise. Feel free to include any comments about your solutions in the answers.txt file.

You are done! Run make submit to submit your answers to the the submission web site.

Acknowledgments

Thanks to Stanford's CS155 course staff for the initial zoobar web application code, which we extended in this lab assignment.