Although 80 and 443 are system ports, how are most web servers able to bind to them anyway?

Solution 1:

There are basically two different approaches:

  1. Initially start running as root, bind to the privileged port, and then drop down to an unprivileged user.

  2. inetd, or xinetd runs privileged, and forwards the requests to web server running unprivileged.

Solution 2:

Since port 80/443 are system ports, meaning they can only be used by privileged users

I think you have it wrong. Anyone can use these ports. Binding to them is a privileged operation.

The rationale here is that some user Joe shouldn't be able to write a malicious web server and then make some host on which he doesn't have any administrative rights. Of course this is a pretty weak model there's usually nothing preventing Joe from putting his own computer on the network, and he could have administrative rights to any machine to which he has physical access.

I'll do a demonstration with netcat.

As an ordinary user, I can't bind to port 80:

$ nc -l -p 80
Can't grab 0.0.0.0:80 with bind : Permission denied

I can bind to port 8080:

$ nc -l -p 8080

Meanwhile in another terminal, I can connect to port 80 and send some data, and see it appear at the server end I just started:

$ nc 127.0.0.1 8080 <<<"Hello world"

If I want to bind to port 80, I need to be root:

$ sudo nc -l -p 80

Or I can assign the CAP_NET_BIND_SERVICE capability to the nc binary:

$ cp `which nc` .
$ sudo setcap 'cap_net_bind_service=+ep' ./nc
$ ./nc -l -p 80

Another option is to write the server program such that after it's called listen() it drops root privileges. This is a pretty common solution, and you will see it with most daemons. Apache, for example, gets started from init as root, and then drops root privileges and becomes the user www-data or something similar once it's bound to port 80. Try running /etc/init.d/apache start as not root and Apache will probably fail to start.