3 hour tutorial

Secure Vintage CGI Applications in C on BSD

Goal

Equip you with sufficient knowledge to build a (simple) secure web application. In C. On a BSD operating system.

Requirements

Conversant in the C programming language. Have a grasp of BSD system administration.

Tools

Participation! Optionally, a laptop.

Introduction

Secure

pledge(2) capsicum(4)
Part III 30 minutes

CGI

CGI FastCGI OpenBSD Apache httpd(8)
Part I 30 minutes

Applications in C

kcgi(3) sqlite(3)
Part II 2 hours

Tutorial notes

Tutorial notes

Link to tutorial online:
Japanese flag kristaps.bsd.lv/absdcon2016

Link to examples:
kristaps.bsd.lv/absdcon2016/examples.tar.gz.

To print, just print in landscape mode.

Questions? Special requests?

Questions? Special requests?

Let's get started.

We'll begin with the introduction: HTTP, CGI, web servers, and operating systems.

Part I: Environment

30 minutes

Part I: Environment

Document Formats

What are HTTP, FastCGI, CGI?

Web server

What is the environment for exchanging HTTP documents?

Operating system

Who is your operating system and what does he do?

Goals

Document formats

FastCGI

FastCGI Specification v1.0

Breaks up an HTTP document into a series of frames, with different frame types for headers and body. Each frame is at most 216 bytes long.

HTTP

RFC 2616

A format divided into the headers and the body. The body can be anything. HTML form data is conveyed either in the requested resource (query string) or within the document body. Other systems (e.g., CalDAV) rely more heavily on the document body and the HTTP method to manipulate resources.

CGI

RFC 3875

Breaks up an HTTP document into a set of environment variables describing the headers and a single document for the HTTP body.

CGI

CGI

  1. a.) web server receives request from client
    1. parses HTTP headers
    2. creates socket pair
    3. forks child
    4. prepares standard input and output
    5. prepares CGI environment
    6. executes application process
  2. b.) application process parses CGI environment
    1. reads HTTP body from standard input
    2. writes HTTP document to standard output
  3. c.) web server forwards HTTP document to client
    1. reads HTTP body from CGI application
    2. reaps CGI application
    3. post-processes HTTP document
    4. writes HTTP document to client

FastCGI

FastCGI

  1. manager opens socket
    1. forks application children
  2. application listens on socket
  3. web server receives request from client
    1. parses HTTP headers
    2. prepares FastCGI stream
    3. writes FastCGI stream to socket
  4. application accept stream
    1. parses HTTP document from stream
    2. writes FastCGI document from stream
  5. web server receives FastCGI document
    1. prepares HTTP document
    2. writes HTTP document to stream

CGI vs. FastCGI

CGI vs. FastCGI

CGI

FastCGI

Pro

  • efficient use of resources: application is re-used so no need for fork(2), execve(2), waitpid(2)
  • flexibility: easier support for off-loading requests
  • Ok, just efficiency... maybe...

Con

  • Yet Another Protocol to parse
  • crash to application bring down entire system (memory leaks are similarly destructive)
  • more features = more complications

Web server

Web server

Short story: accepts an HTTP request from a client and generates a response, also in HTTP.

Long story: process management and high-throughput I/O into and out of CGI and FastCGI processes.

Web server operation

Web server operation

  1. receives and parses request from client
  2. maps requested resource (e.g., /cgi-bin/foo) into a terminal resource (e.g., /var/www/cgi-bin/foo)
  3. invokes the terminal resource handler (e.g., mod_cgi.so for Apache2)
  4. manages I/O to (request) and from (response) the resource
  5. constructs (or just post-processes) HTTP document from response
  6. writes HTTP document to client

Web server: Apache

Web server: Apache

The Apache browser has two basic flavours on BSD: the current version available from the Apache project (Apache2) and the legacy version forked by OpenBSD (Apache1.3).

In the former case, the server configuration is in /etc/apache2/httpd.conf. In the latter case, /var/www/conf/httpd.conf.

Web server: httpd

Web server: httpd

OpenBSD's httpd, httpd(8), inherits from relayd(8) and was recently imported into OpenBSD after an aborted attempt to use nginx instead of the legacy Apache1.3.

Its configuration file is in /etc/httpd.conf.

Operating systems

Operating systems

The operating system brokers resources to and from the webserver, such as...

  • static file server
  • CGI (and managed FastCGI) processes

We'll focus primarily on OpenBSD, and care about the operating system to the extent of the security measures it provides.

Operating system resources

Operating system resources

A well-configured operating system can protect its non-web resources by enacting sandboxes. These fence the resources in a variety of ways. OpenBSD gives us privilege separation (users), file-system jail (files), and general sandboxing (system calls, files).

Operating system sandboxes: file-system

Operating system sandboxes: file-system

The chroot(2) system call protects us from accessing files outside of a directory sub-tree. This is mostly invoked by the web server or FastCGI manager. By default, httpd(8) will jail us to /var/www.

Operating system sandboxes: privilege drop

Operating system sandboxes: privilege drop

By invoking the setgid(2) and setuid(2) system call, we can protect ourselves from accessing resources owned by other users. Thus, if there are other users' files in our file-system jail, we can't access them.

Both file-system jails and privilege dropping help us with files, but don't do much more.

Operating system sandboxes: sandboxing

Operating system sandboxes: sandboxing

The new pledge(2) call (née tame(2) in 5.8) allows a much broader class of sandboxing. Another OpenBSD facility is systrace(4), which is more fine-grained, but difficult to control.

Part I: Conclusion

Part I: Conclusion

This concludes Part I. We'll next walk through a base installation of OpenBSD and how to set up our environment.

Part II: Applications

2 hours

Part II: Applications

Rationale

Why C? (Why not C?)

Environment

How do we prepare a CGI environment?

Development

How do we develop our CGI application?

Deployment

How do we deploy our CGI application in our environment?

Case study

A case study

Why C?

Why C?

manpages
  • superb OpenBSD manpages.
  • most C libraries have a tradition of manpages (notable exceptions: SQLite)
OS-level support
  • all operating system capabilities are available to C applications
third-party components
standardised
  • C does not change at the whim of a company or individual
fewer hipsters
  • hipsters dislike memory managment

Why Not C?

Why Not C?

upgrade cycle
  • upgrading operating system may cause your application to fail (e.g., ABI breakage) even if it's statically linked
  • dynamically linked applications may fail because their dependent libraries might disappear
  • chroot(2) will need refreshing as dependent libraries change
third-party components
  • the considerable web-technology components for Perl, Python, etc.

Environment

Environment

We'll start with a fresh OpenBSD installation with the httpd(8) system already in base.

  1. edit /etc/doas.conf to do anything
  2. edit /etc/httpd.conf with a minimal static file to test
  3. test by manually starting httpd(8)
  4. edit /etc/httpd.conf with our document root and indicate that we'll use FastCGI
  5. manually start slowcgi(8) and httpd(8)
  6. use rcctl(8) to start httpd(8) and slowcgi(8) on boot

Environment: httpd

Environment: httpd

Configure a directory as the root server directory. This is relative to the chroot /var/www.

ext_addr="egress"
prefork 2
server "localhost" {
	listen on $ext_addr port 80
	root "/htdocs"
}

This configures /var/www/htdocs as our document root.

Environment: httpd

Environment: httpd

Let's now add httpd(8) to the startup sequence and start it.

doas rcctl enable httpd
doas rcctl start httpd
doas rcctl check httpd

Environment: httpd

Environment: httpd

Why is slowcgi(8) necessary? It lets us emulate CGI in a FastCGI context. (httpd(8) supports only FastCGI natively.)

Environment: httpd

Environment: httpd

Next, configure a FastCGI location. We will use the slowcgi(8) system.

ext_addr="egress"
prefork 2
server "localhost" {
	listen on $ext_addr port 80
	root "/htdocs"
        location "/cgi-bin/*" {
                fastcgi
                root "/"
        }
}

This means that our scripts must go in /var/www/cgi-bin.

Environment: httpd

Environment: httpd

Let's now add slowcgi(8) to the startup sequence and start it. We then re-start httpd(8) to pick up the changes.

doas rcctl enable slowcgi
doas rcctl start slowcgi
doas rcctl check slowcgi
doas rcctl restart httpd

Development

Development

Let's start with a simple example, example1.c. It'll help to have the RFC 2616 (HTTP) and RFC 3875 (CGI) standards documents handy.

#include <stdlib.h>
#include <stdio.h>

int
main(void)
{

	puts("Status: 200 OK\r");
	puts("Content-Type: text/html\r");
	puts("\r");
	puts("Hello, world!");
	return(EXIT_SUCCESS);
}

Deployment

Deployment

Next we get our CGI script, example1.c, ready for action. Then we'll install it in the configured CGI root.

cc -static -g -W -Wall -o example1 example1.c
sudo install -u www -g www -m 0500 example1 /var/www/cgi-bin

Did it work?

Quiz

Quiz

Write a CGI script in C that will output all CGI variables in an HTML document. For example:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Cache-Control: max-age=0
Connection: keep-alive
Host: localhost
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; U; Android 4.0; en-us;...

Environment

Environment

Let's simplify CGI handling by using kcgi. Why? So we don't need to handle RFC 2616, RFC 3875, etc.

doas pkg_add -v kcgi

Disclaimer: I wrote kcgi(3), available at kristaps.bsd.lv/kcgi.

Development

Development

Let's prepare the same with kcgi(3) as example2.c. It's overkill for our purposes now, but later, we'll use this a lot more.

#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <kcgi.h>

int 
main(void) 
{
	struct kreq r;
	const char *page = "index";
	if (KCGI_OK != khttp_parse(&r, NULL, 0, &page, 1, 0))
		return(EXIT_FAILURE);
	khttp_head(&r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]);
	khttp_head(&r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[r.mime]);
	khttp_body(&r);
	khttp_puts(&r, "Hello, world!\n");
	khttp_free(&r);
	return(EXIT_SUCCESS);
}

Deployment

Deployment

Now let's install the file.

cc -static -g -W -Wall -o example2 example2.c -lkcgi -lz
sudo install -u www -g www -m 0500 example2 /var/www/cgi-bin

Did it work?

If you're on an older OpenBSD machine, you'll need /dev/systrace in your chroot(2).

Quiz

Quiz

Amend your earlier quiz to print out CGI variables and form elements.

Case study

Case study

We'll now begin a longer case study, available as example3.c.

At the end of this part, you'll be familiar with how to parse form values and handle page requests.

Quiz

Quiz

Modify the case study to add a page, printenv, that prints CGI variables and form request variables. For example:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Cache-Control: max-age=0
Connection: keep-alive
Host: localhost
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Linux; U; Android 4.0; en-us;...
Form[integer,0] = 123
Form[integer,1] = 321

Case study

Case study

We'll now begin another case study, which may be downloaded as example4.c.

In this study, we'll also use the SQLite database to record a stored value. The database system is pre-installed with OpenBSD systems. You'll need to append -lsqlite3 to the compilation routine, and execute the following additional steps to prepare for the database.

doas mkdir /var/www/tmp
doas chmod 1777 /var/www/tmp

Quiz

Quiz

Modify the previous example to include the last host to change the counter and when it was changed.

Quiz

Quiz

For the last quiz, see if you can modify the previous example to serve a JSON resource (hint: use kcgijson(3)), and have an HTML front-end (in /var/www/htdocs) asynchronously load format the output.

function loadJSON(path, success, error)
{
	var xhr = new XMLHttpRequest();
	xhr.onreadystatechange = function() {
		if (xhr.readyState === XMLHttpRequest.DONE) {
			if (xhr.status === 200) {
				success(JSON.parse(xhr.responseText));
			} else {
				error(xhr);
			}
		}
	};
	xhr.open("GET", path, true);
	xhr.send();
}

loadJSON('/cgi-bin/quiz4.json',
	function(data) { console.log(data); },
	function(xhr) { console.error(xhr); }
);

Part II: Conclusion

Part II: Conclusion

This concludes Part II. We'll next modify our case study applications to use the operating system's sandbox mechanism.

Part III: Security

30 minutes

Part III: Security

Our CGI scripts inherit the privilege-separation and chroot(2) environment of the web server. Can we do better?

We'll use the pledge(2) system call, introduced in OpenBSD 5.9.

Security measures

Security measures

Case study

Case study

Let's start with our simple sandbox example, example5.c. (The example file also includes a Darwin sandbox.)

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int
main(void)
{

	if (-1 == pledge("stdio", NULL)) 
		return(EXIT_FAILURE);
	printf("Status: 200 OK\r\n");
	printf("Content-Type: text/html\r\n");
	printf("\r\n");
	printf("Hello, world!\n");
	return(EXIT_SUCCESS);
}

Security measures

Security measures

Case study

Case study

Let's prepare the same with kcgi(3) as example6.c. (The example file also includes a Darwin sandbox.)

#include <stdint.h>
#include <stdlib.h>
#include <kcgi.h>

int 
main(void) 
{
	struct kreq r;
	const char *page = "index";
	if (KCGI_OK != khttp_parse(&r, NULL, 0, &page, 1, 0))
		return(EXIT_FAILURE);
	if (-1 == pledge("stdio", NULL)) 
		return(EXIT_FAILURE);
	khttp_head(&r, kresps[KRESP_STATUS], 
		"%s", khttps[KHTTP_200]);
	khttp_head(&r, kresps[KRESP_CONTENT_TYPE], 
		"%s", kmimetypes[r.mime]);
	khttp_body(&r);
	khttp_puts(&r, "Hello, world!\n");
	khttp_free(&r);
	return(EXIT_SUCCESS);
}

Case study

Case study

Finally, let's sandbox our longer SQLite example in example7.c. (The example file also includes a Darwin sandbox.)

Part III: Conclusion

Part III: Conclusion

This concludes Part III. Questions? Comments?

Thank you for attending!