X

News, tips, partners, and perspectives for the Oracle Solaris operating system

Locking Down Apache

Guest Author

I noticed my Apache web server had one process that ran as root, which then forked other processes as user webservd:

bleonard@os200906:~$ ps -ef | grep httpd
webservd 853 850 0 14:05:43 ? 0:00 /usr/apache2/2.2/bin/httpd -k start
webservd 852 850 0 14:05:43 ? 0:00 /usr/apache2/2.2/bin/httpd -k start
webservd 851 850 0 14:05:43 ? 0:00 /usr/apache2/2.2/bin/httpd -k startroot 850 1 1 14:05:42 ? 0:00 /usr/apache2/2.2/bin/httpd -k start
webservd 854 850 0 14:05:43 ? 0:00 /usr/apache2/2.2/bin/httpd -k start
webservd 855 850 0 14:05:43 ? 0:00 /usr/apache2/2.2/bin/httpd -k start

The reason for this is that apache wants access to port 80, which traditionally requires root privileges. To improve upon this all-or-nothing security model, Solaris 10 introduced the concept of fine-grained privileges, and in OpenSolaris there are now 75 of them:

bleonard@os200906:~$ ppriv -l 
contract_event
contract_identity
contract_observer
cpc_cpu
dtrace_kernel
dtrace_proc
dtrace_user
file_chown
file_chown_self
file_dac_execute
file_dac_read
file_dac_search
file_dac_write
file_downgrade_sl
file_link_any
file_owner
file_setid
file_upgrade_sl
file_flag_set
graphics_access
graphics_map
ipc_dac_read
ipc_dac_write
ipc_owner
net_bindmlp
net_icmpaccess
net_mac_aware
net_observability
net_privaddr
net_rawaccess
proc_audit
proc_chroot
proc_clock_highres
proc_exec
proc_fork
proc_info
proc_lock_memory
proc_owner
proc_priocntl
proc_session
proc_setid
proc_taskid
proc_zone
sys_acct
sys_admin
sys_audit
sys_config
sys_devices
sys_ipc_config
sys_linkdir
sys_mount
sys_dl_config
sys_ip_config
sys_net_config
sys_nfs
sys_res_config
sys_resource
sys_smb
sys_suser_compat
sys_time
sys_trans_label
virt_manage
win_colormap
win_config
win_dac_read
win_dac_write
win_devices
win_dga
win_downgrade_sl
win_fontpath
win_mac_read
win_mac_write
win_selection
win_upgrade_sl
xvm_control

To get a description of each privilege, run ppriv -lv.

What this means is that I can now give a process, which has traditionally run with root privileges, just the privileges it needs to get its job done - a concept known as least privilege. The trick, of course, is figuring out which privileges a process needs.

If we look at the privileges for process 850 above, we see that it has them all:

bleonard@os200906:~$ pfexec ppriv 850
850:

/usr/apache2/2.2/bin/httpd -k start
flags = <none>

E: all

I: basic

P: all

L: all

Let's try to start Apache as a typical non-privileged user, webservd, to see what happens. First, make sure Apache's not already running:

bleonard@os200906:~$ svcadm disable apache22

Then, switch to user webservd and try to start the server:

bleonard@os200906:~$ pfexec su - webservd
webservd@os200906:~$ /usr/apache2/2.2/bin/apachectl start
(13)Permission denied: make_sock: could not bind to address [::]:80
(13)Permission denied: make_sock: could not bind to address 0.0.0.0:80
no listening sockets available, shutting down
Unable to open log

To see exactly which permission is needed for Apache to bind to port 80, run the command again with privilege debugging:

webservd@os200906:~$ ppriv -eD /usr/apache2/2.2/bin/apachectl start
httpd[885]: missing privilege "proc_priocntl" (euid = 80, syscall = 112) needed at secpolicy_setpriority+0x24
httpd[885]: missing privilege "ALL" (euid = 80, syscall = 80) needed at zfs_zaccess+0x1fc
httpd[885]: missing privilege "net_privaddr" (euid = 80, syscall = 232) needed at tcp_bind_select_lport+0xbb
(13)Permission denied: make_sock: could not bind to address [::]:80
httpd[885]: missing privilege "net_privaddr" (euid = 80, syscall = 232) needed at tcp_bind_select_lport+0xbb
(13)Permission denied: make_sock: could not bind to address 0.0.0.0:80
no listening sockets available, shutting down
Unable to open logs

You can see the description for the net_privaddr privilege as follows:

webservd@os200906:~$ ppriv -lv net_privaddr
net_privaddr

Allows a process to bind to a privileged port

number. The privilege port numbers are 1-1023 (the traditional

UNIX privileged ports) as well as those ports marked as

"udp/tcp_extra_priv_ports" with the exception of the ports

reserved for use by NFS.

So, exit out of the webservd user account (the webservd user doesn't have the rights to add additional privileges) and add the net_privaddr privilege to user webservd:

bleonard@os200906:~$ pfexec usermod -K defaultpriv=basic,net_privaddr webservd

Then log in again to webservd and try starting Apache again:

bleonard@os200906:~$ pfexec su - webservd
webservd@os200906:~$ /usr/apache2/2.2/bin/apachectl start
(13)Permission denied: httpd: could not open error log file /var/apache2/2.2/logs/error_log.
Unable to open log

The remaining error is an easy one to fix. The /var/apache2/2.2/logs directory is already owned by webservd. It's just that root has previously created its log files there. Either delete these files or change their ownership and the server should start successfully:

webservd@os200906:~$ /usr/apache2/2.2/bin/apachectl start               
webservd@os200906:~$ ps -ef | grep httpd
webservd 914 911 0 15:16:05 ? 0:00 /usr/apache2/2.2/bin/httpd -k start
webservd 916 911 0 15:16:05 ? 0:00 /usr/apache2/2.2/bin/httpd -k start
webservd 917 911 0 15:16:05 ? 0:00 /usr/apache2/2.2/bin/httpd -k start
webservd 915 911 0 15:16:05 ? 0:00 /usr/apache2/2.2/bin/httpd -k start
webservd 911 1 1 15:16:04 ? 0:00 /usr/apache2/2.2/bin/httpd -k start
webservd 913 911 0 15:16:05 ? 0:00 /usr/apache2/2.2/bin/httpd -k start

So, the next question is how do you bring these changes to Apache when started with SMF? Glenn Brunette has an excellent white paper that address this topic titled: Limiting Service Privileges in the Solaris 10 Operating System. However, I'll hit the highlights for you here.

First, stop Apache:

 /usr/apache2/2.2/bin/apachectl stop

Then look at the existing SMF properties defined for starting the service:

bleonard@os200906:~$ svccfg -s apache22
svc:/network/http:apache22> listprop start
start method
start/exec astring "/lib/svc/method/http-apache22 start"
start/timeout_seconds count 60
start/type astring method

Now, save the additional properties that we want our service to have to a text file. I'm calling mine apache_cfg:

bleonard@os200906:~$ cat apache_cfg 
select apache22
setprop start/user = astring: webservd
setprop start/group = astring: webservd
setprop start/privileges = astring: basic,!proc_session,!proc_info,!file_link_any,net_privaddr
setprop start/limit_privileges = astring: :default
setprop start/use_profile = boolean: false
setprop start/supp_groups = astring: :default
setprop start/working_directory = astring: :default
setprop start/project = astring: :default
setprop start/resource_pool = astring: :default
end

In the above we're setting a bunch of additional properties for the Apache service, they key ones being start/user, start/group and start/privileges (note, the privileges changes we made earlier to user webservd are no longer necessary. The SMF framework does not consider a user's privileges when starting process, but rather the privileges as defined in the manifest.). However, all of the other properties (set to :default) need to be there for them to work properly.

Then run the following command to apply the property changes:

svccfg -f apache_cfg

To see the effect:

bleonard@os200906:~$ svccfg -s apache22
svc:/network/http:apache22> listprop start
start method
start/exec astring "/lib/svc/method/http-apache22 start"
start/timeout_seconds count 60
start/type astring method
start/user astring webservd
start/group astring webservd
start/privileges astring basic,!proc_session,!proc_info,!file_link_any,net_privaddr
start/limit_privileges astring :default
start/use_profile boolean false
start/supp_groups astring :default
start/working_directory astring :default
start/project astring :default
start/resource_pool astring :default
svc:/network/http:apache22> quit

Then commit the changes and enable Apache:

bleonard@os200906:~$ svcadm refresh apache22
bleonard@os200906:~$ svcadm enable apache22
bleonard@os200906:~$ ps -ef | grep httpd
webservd 1037 1 2 15:52:14 ? 0:00 /usr/apache2/2.2/bin/httpd -k start
webservd 1038 1037 0 15:52:15 ? 0:00 /usr/apache2/2.2/bin/httpd -k start
webservd 1041 1037 0 15:52:15 ? 0:00 /usr/apache2/2.2/bin/httpd -k start
webservd 1039 1037 0 15:52:15 ? 0:00 /usr/apache2/2.2/bin/httpd -k start
webservd 1040 1037 0 15:52:15 ? 0:00 /usr/apache2/2.2/bin/httpd -k start
webservd 1042 1037 0 15:52:15 ? 0:00 /usr/apache2/2.2/bin/httpd -k start

Now check the privileges on one of the Apache processes:

bleonard@os200906:~$ pfexec ppriv 1037
1037:

/usr/apache2/2.2/bin/httpd -k start
flags = <none>

E: basic,!file_link_any,net_privaddr,!proc_info,!proc_session

I: basic,!file_link_any,net_privaddr,!proc_info,!proc_session

P: basic,!file_link_any,net_privaddr,!proc_info,!proc_session

L: all

Note, the basic privilege set includes:

file_link_any
proc_exec
proc_fork
proc_info
proc_session

The basic set was created for backwards compatibility. Prior to Solaris 10, all users had those 5 privileges, so going forward those 5 privileges are still given to all users as part of the basic privilege set. The difference is that now one or more of these privileges can be taken away, as was done to user webservd above with file_link_any, proc_info and proc_session.

Join the discussion

Comments ( 11 )
  • Constantin Gonzalez Tuesday, May 11, 2010

    Hi Brian,

    great article, I learned a lot about privileges from it!

    Now, wouldn't it be nice if ppriv -eD also told you what privileges the process did _not_ use?

    That would help eliminate the guesswork for getting rid of file_link_any, proc_info and proc_session in the example above...

    Cheers,

    Constantin


  • ptecza Tuesday, May 11, 2010

    Hello Brian,

    I'm very glad to see that Observatory blog is still alive :D

    My best regards,

    Pawel


  • Brian Leonard Tuesday, May 11, 2010

    Yes, sorry for the hiatus. I've just been slammed since the acquisition :-).


  • Constantin Gonzalez Tuesday, May 11, 2010

    Thanks @niceness!


  • Cyril Galibern Tuesday, May 11, 2010

    Hello Brian,

    Very pleased To see The Observatory is back.

    Thanks

    Cyril


  • ptecza Wednesday, May 12, 2010

    Brian,

    you have restored my faith that OpenSolaris is not dead ;)

    Thank you! :)


  • Jason Wednesday, May 26, 2010

    How do you get around the need to create /var/run/apache2/2.2 prior to starting apache?

    If you start the service (prior to changing the start/user property), then stop, make the property changes, and restart, all is well, but /var/run gets cleared during a reboot, and when apachectl runs again (this time as webservd), it won't have permissions to create the directory + chown it.

    I've been thinking of making a simple service that will take care of this (and making apache dependent on it), but was curious what solution you used.


  • Brian Leonard Thursday, May 27, 2010

    Jason,

    Apache is starting successfully for me after a reboot, but I'm not sure I understand why at this point. /var/run/apache2 is somehow still created as root. Then the 2.2 directory and http.pid file are owned by webservd. Have you verified it doesn't also work for you?


  • Jason Thursday, May 27, 2010

    Yes -- in fact, I had made the smf changes prior to ever enabling http:apache22 (new install), and encountered it and stumbled across that.


  • Leo Thursday, November 24, 2011

    Thank you so much, it really helped me


  • maz Thursday, June 5, 2014

    Hello,

    Thank you this has been very very useful!!!

    I wanted the settings in a manifest, so here's what I put in (so far so good):

    <exec_method timeout_seconds="60" type="method" name="start"

    exec="/opt/apache2/2.4.9/bin/apachectl start">

    <method_context>

    <method_credential user='webservd' group='webservd' privileges='basic,!proc_session,!proc_info,!file_link_any,net_privaddr'/>

    </method_context>

    </exec_method>

    Any comments gratefully received!


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.