BigAdmin System Administration Portal
Community-Submitted Article
Print-friendly VersionPrint-friendly Version
This content is submitted by a BigAdmin user. It has not been reviewed for technical accuracy by Sun Microsystems, though it may have been lightly edited to improve readability. If you find an error or would like to comment on the article, please contact the submitter or use the comment field at the bottom of the article. Community submissions may not follow Sun trademark guidelines. For information on Sun trademarks, please see http://www.sun.com/suntrademarks/.
 
 

Enabling Web Applications to Run Scripts as Root Using Apache on the Solaris 9 and 10 OS

Vincent Esposito, October 2008

This article provides information on enabling web applications to run scripts as root -- without creating a giant security hole -- while using the Apache web server with the Solaris 9 or 10 OS and Solaris JumpStart software.

This article covers the following topics:

Introduction: A "Simple" Problem

One of my recent pet projects was to build a simple web-based interface for a Solaris JumpStart server, based on the Solaris OS for SPARC platforms, using server-side scripting (and a little JavaScript on the client side) to walk a user through a menu system that ultimately runs add_install_client (or, inversely, rm_install_client) with the appropriate parameters.

The process of building this interface turned out to be an education, one offering new insights into the workings of Apache, Solaris JumpStart software, Solaris execution permissions, and Rights Based Access Control (RBAC).

In this article, I walk through the trials and errors I encountered along the way. I hope to pass on the insights I've gained. Prior to taking on this project, I had very limited experience working with Apache, and was unaware of the issues I would face. Now that I've gained some experience in this area, I can see that I knew less than I originally thought I knew.

Please do not expect to become an expert at deploying Apache on the Solaris platform by reading this article. You should expect to come away with a basic understanding of the concepts required to solve problems related to execution permissions for server-side scripts and to begin exploring the deeper mysteries of the topic.

I originally built my solution on a Solaris JumpStart server running the Solaris 9 09/05 release. For demonstration purposes, I ran through the process on a server running the Solaris 10 08/07 release. Aside from a few minor details involving service startup, the same basic process applied, so I will point out the few instances where commands differ between the two versions.

Setting Up the Platform

We will assume that we are starting with a working Solaris JumpStart server built on either the Solaris 9 or 10 OS installed with the "Full plus OEM" Software Group, and that the JumpStart server is connected to a network using the IP address 10.1.1.1 with a 24-bit netmask (255.255.255.0).

We will assume that the server has a /jumpstart file system that is shared through NFS with the read-only and anon=0 options. We will also assume that the JumpStart server was built from the Solaris 10 DVD using the following command:

# /cdrom/cdrom0/s0/Solaris_10/Tools/setup_install_server /jumpstart/10

After setup_install_server finishes running, you will have a Solaris JumpStart server capable of installing the Solaris 10 OS on your JumpStart clients, and the installation media directory will be /jumpstart/10. If your JumpStart server was set up previously, you may omit this setup_install_server step, and continue on using the installation directories appropriate for your environment if they differ from the ones used in our example.

We need to put a few items in place in order to easily run an install on a remote client. We will need the Ethernet address of the client machine and a corresponding IP address and host name. For the purposes of this demonstration, we will assume that client machine's architecture is sun4u with an Ethernet address of 8:0:20:2a:3b:fc. We will assign the IP address 10.1.1.5 to our client, and call it sample_client.

Following the basic requirements for Solaris JumpStart software, we need to enter the Ethernet address and host name in /etc/ethers and enter the IP address and host name in /etc/hosts on the JumpStart server:

# echo 8:0:20:2a:3b:fc sample_host >> /etc/ethers
# echo 10.1.1.5 sample_host >> /etc/hosts

Next, we will build very simple Solaris JumpStart configuration files. We will create a directory called /jumpstart/config to contain the rules file and profile and a directory called /jumpstart/sysid to hold a copy of the sysidcfg file:

# mkdir -p /jumpstart/config
# mkdir -p /jumpstart/sysid

We need to create a basic sysidcfg file under /jumpstart/sysid. The configuration file we will create will set the client's time zone, locale, and terminal type, and it will set the root password to root.

# vi /jumpstart/sysid/sysidcfg

In the vi editor, enter and save the following:

timezone=US/Eastern
timeserver=localhost
terminal=vt100
name_service=NONE
network_interface=PRIMARY
{
	protocol_ipv6=no
	netmask=255.255.255.0
	default_route=none
}
root_password="6eBUcyPtDTMuI"
security_policy=NONE
nfs4_domain=dynamic
service_profile=open

This sysidcfg file represents a very basic configuration. The exact contents applicable to your actual environment might differ.

In the /jumpstart/config directory, we will create a simple JumpStart rules file and profile file. For demonstration purposes, we will use the simplest possible rules file--one that will direct any server to use the profile we will create without using any additional begin or finish scripts.

# vi /jumpstart/config/rules

Enter the following line and save the file:

any	-	-	profile	-

Now create the profile:

# vi /jumpstart/config/profile

Enter the following lines and save the file:

install_type	initial_install
cluster		SUNWCXall
system_type	standalone
partitioning	explicit
filesys	any	free	/
filesys	any	swap	2048

For demonstration purposes, this profile will build the client system using a 2-gigabyte swap partition and assign all remaining space on the primary disk to the / file system.

This completes the build of a testable JumpStart configuration. Since we want to be able to have a web-based script execute the add_install_client command, we will need to configure and start Apache.

A working installation of Apache is included in the "Full plus OEM" Software Group for both the Solaris 9 and 10 OS, so we do not have to worry about downloading the Apache source code and compiling. We simply need to put a valid configuration file in place and start the Apache service.

Note: The Solaris 10 OS ships with both Apache version 1.3 and Apache version 2.0. The only difference in the procedure is that all the directories associated with version 2.0 are in the form /*/apache2 rather than /*/apache. Also, the Solaris 10 OS assumes that you will use Apache 2.0, so the service management facility contains only a pre-configured service definition for Apache 2.0. The 1.3 version is started using the old-style /etc/init.d script, and version 2 is started using the svcadm command.

The Apache distribution included in the Solaris 9 and 10 OS has a sample httpd.conf file that will allow you to get started easily by using default settings.

To enable Apache, simply copy the sample httpd.conf file located in /etc/apache (or /etc/apache2) to /etc/apache/httpd.conf (or /etc/apache2/httpd.conf) before attempting to start the Apache service:

To start Apache 1.3 on the Solaris 9 or 10 OS, use the following two commands:

# cp /etc/apache/httpd.conf-example /etc/apache/httpd.conf
# /etc/init.d/apache start

Or, to start Apache 2.0 on the Solaris 10 OS, use the following two commands:

# cp /etc/apache2/httpd.conf-example /etc/apache2/httpd.conf
# svcadm enable svc:/network/http:apache2

Note: The rest of this document will assume we are using Apache version 1.3. If you wish to use version 2.0 to go through the remaining examples, you will need to remember that the httpd.conf file lives in /etc/apache2/httpd.conf and that the cgi-bin and htdoc directories live in /var/apache2 rather than /etc/apache and /var/apache.

To verify that Apache is running, start a web browser and enter http://10.1.1.1 in the location bar. You should see the Apache test/welcome page load.

We will also need a script built in the cgi-bin directory that will attempt to run add_install_client and show us the output in the body of the web page it generates. The usual practice for web developers is to write server-side scripts such as this one in either the Common Gateway Interface (CGI) or Perl languages. Since JumpStart exists on the Solaris OS only, we can choose a more familiar scripting language, such as korn shell, to accomplish the same task.

We will also have this script print the output of the id command, so we can see our real user ID (UID), group ID (GID), and effective user ID (EUID) when testing our solution later on. We will also use the cat command to show the contents of /etc/bootparams for troubleshooting purposes. Server-side scripts normally live in /var/apache/cgi-bin, so we will place our script there.

# vi /var/apache/cgi-bin/testuid.ksh

Type the following, save the file, and exit:

#!/bin/ksh
print "Content-type: text-plain"
print ""
print ""
print `id`
print ""
print "results of add_install_client"
/jumpstart/10/Solaris_10/Tools/add_install_client \
-c 10.1.1.1:/jumpstart/config \
-p 10.1.1.1:/jumpstart/sysid sample_client sun4u
print ""
print "Contents of /etc/bootparams"
print ""
cat /etc/bootparams

After saving, add execution permissions for all users so the script can actually be invoked:

# chmod +x /var/apache/cgi-bin/testuid.ksh

After all this work, we are ready to point a web browser at our script and see what happens. Type http://10.1.1.1/cgi-bin/testuid.ksh in the location bar of your web browser...and see our first failure of the day:

output of the id command:
uid=60001(nobody) gid=60001(nobody)

output of add_install_client:
You must be root to run
/jumpstart/10/Solaris_10/Tools/add_install_client

Contents of /etc/bootparams:

If we examine the add_install_client script itself using more, we will find an if-then statement that explicitly checks the UID number the script is run under and immediately fails if the UID listed in the output of the id command is anything other than 0 (root).

This should not come as a surprise: add_install_client needs to touch several system configuration files, stop services, and start services. As our output shows, Apache runs as user 60001 (nobody), which does not have the permissions needed to perform these tasks.

Possible Solutions and Why They Should Not Be Used

Caution: The solutions described in this section might pose significant security risks depending on the exact configuration of your environment. While it is possible to further adjust and combine elements of these solutions to provide at least partial functionality, you might be compromising security. These solutions are being presented as examples of what not to do, rather than as suggestions for ways of obviating security for the sake of convenience.

Attempt to Run Apache as Root

The first, and seemingly most simple, solution to attempt is to run the web server as user ID root. The process for running Apache as a user other than the default (nobody) is simple: Stop the web server, make a small change to the httpd.conf file, and restart.

First, stop Apache:

# /etc/init.d/apache stop

Edit the httpd.conf file:

# vi /etc/apache/httpd.conf

Search for two lines that read as follows:

User nobody
Group nobody

These lines define the user ID and group ID that the web server will run as. To run Apache as user ID root, change these lines to this:

User root
Group sys

Save the file and exit vi. Now, we can start Apache again, and test our solution:

# /etc/init.d/apache start

Unfortunately, the web server will no longer start. If we drill down through the start-up process outlined in the /etc/init.d/apache script, the error message indicates that Apache will refuse to start as root:

httpd starting
Error:	Apache has not been designed to serve pages while
running as root. There are known race conditions that
will allow any local user to read any file on the system.
If you still desire to serve pages as root then
add -DBIG_SECURITY_HOLE to the EXTRA_CFLAGS line in your
src/Configuration file and rebuild the server. It is
strongly suggested that you instead modify the User
directive in your httpd.conf file to list a non-root
user.

/usr/apache/bin/apachectl start: httpd could not be started

After reading through the Apache documentation, we quickly learn that Apache is intentionally designed to fail when run as root due to the huge potential security risks involved. Basically, if the web server runs as root, server-side processes run by the web server have unrestricted access to every aspect of the system. Files can be deleted or modified either intentionally or unintentionally, for example. This opens up the possibility of attack by malicious code if the web server is being run on a network exposed to the outside world.

There is, however, a workaround for this to cover the rare circumstances where running Apache as root is necessary. The designers of Apache, however, did not make this process easy to perform, perhaps intentionally. The general message that can be inferred is "this is a bad idea, and we're going to make it as difficult as possible to do things this way."

According to the published documentation, enabling Apache to run as root involves re-compiling Apache from the source code with additional options enabled. The specific option required is a flag entitled -DBIG_SECURITY_HOLE--again, another hint that this is not a usable solution.

At this point, there is little point in continuing with this solution. Even if we jump through all the necessary hoops to run Apache as root, we are left with a server highly vulnerable to malicious attack or damage to system integrity through accident or a server-side script that contains unfixed bugs.

Since there are a number of other potential solutions that are not as risky or as difficult to implement, we will abandon this train of thought and move on to our next potential solution.

Before continuing, we will reset the User and Group statements in httpd.conf and restart the server to normalize things:

Stop the web server:

# /etc/init.d/apache stop

Edit httpd.conf:

# vi /etc/apache/httpd.conf

Change this:

User root
Group sys

To this:

User nobody
Group nobody

Save the file and exit. Then restart Apache:

# /etc/init.d/apache start

Use the setuid Bit

Another solution that we can attempt involves setting the setuid permission bit for the critical scripts we need to run as root. There are two potential places we can attempt this: both our testuid.ksh script and the actual add_install_client and rm_install_client scripts.

We will attempt this solution several ways:

  • Just setting the setuid bit on our testuid.ksh script
  • Just setting it on add_install_client
  • Setting it on both at the same time

Basically, the setuid bit allows users to execute a script or binary as if they were the owner of the file, in this case, root. In theory, this might solve our problem.

Using the setuid bit is not recommended in general, and should be done only in very limited and controlled situations, because it opens up an avenue for attackers to gain at least partial root access, and it means tracking changes in file permission settings in case we need to rebuild the JumpStart portion of the server.

While we cannot address the possible security hole this can create, most JumpStart servers are generally on private networks or otherwise not exposed to the Internet. So security might not be an overriding concern. As for managing file permissions, we will be dealing with only a very limited number of known files, so while this introduces a few management and record-keeping headaches, they are relatively small issues.

Our core concerns for this potential solution are to ensure that it actually functions as we think it should.

For our first attempt, we will add the setuid permission to our testuid.ksh script and analyze the results. We can perform this task with the chmod command.

First check the current owner and permissions on the testuid.ksh file:

# cd /var/apache/cgi-bin
# ls -l testuid.ksh
-rwxr-xr-x  1 root root 344 Jun 14 20:19 testuid.ksh

We can see that our script's permissions currently allow any user to execute it, and it is owned by root. From here, we should simply have to turn on the setuid bit to allow any user to run the script as root:

# chmod u+s testuid.ksh
# ls -l testuid.ksh
-rws-r-xr-x  1 root root 344 Jun 14 20:19 testuid.ksh

If we look at our ls -l output now, we will see an s in the fourth permission slot (usually indicates user execution permission). This indicates that the setuid bit has been set successfully.

Now, to test our solution, we can start a web browser, point it at http://10.1.1.1/cgi-bin/testuid.ksh, and observe the results.

output of the id command:
uid=60001 (nobody) gid=60001 (nobody) euid=0 (root)

output of add_install_client:
You must be root to run
/jumpstart/10/Solaris_10/Tools/add_install_client

Contents of /etc/bootparams:

This portion of the solution has failed; however, the output of the id command generated gives us an indication of what the problem is, and it gives us further insight into the workings of the setuid bit.

Note that the web server is still running the script as UID 60001 (nobody); however, id now returns an EUID (effective user id) value as well. Normally, having your EUID set to root is sufficient for most applications; however, there might be a check or other requirement within add_install_client itself that demands that the UID (real user ID) rather than the EUID (effective user ID) is root.

If we use the more command to look at the add_install_client script, we can quickly find the reason for the failure.

# more /jumpstart/10/Solaris_10/Tools/add_install_client

If we scroll down several pages and find the section following the comment line that reads # MAIN - program, we will see that the very first thing that add_install_client does is check the real user ID the script is running as.

Output of the more command:

#
# MAIN - Program
#

myname=$0
ID=`id`
USER=`expr "${ID}" : 'uid=\([^(]*\).*'`
unset NAME1 NAME2 IP_ADDR ETHER_ADDR PRODUCT_SERVER
PRODUCT_PATH IMAGE_PATH tools_path
unset SYSID_CONFIG_SERVER SYSID_CONFIG_PATH CONFIG_SERVER
CONFIG_PATH NS_POLICY BOOT_FILE

trap abort $SIGHUP $SIGINT $SIGQUIT $SIGTERM

# verify user ID before proceeding - must be root
#
if [ "${USER}" != "0"]; then
	echo "You must be root to run $0"
	cleanup_and_exit 1
fi

Take note of the lines starting with ID= and USER=. These lines execute the id command and specifically parse out the number following uid=. The if-then statement a few lines later then checks to see if this value is 0, indicating a real user ID of root. As you can see from the output of the id command in our test run, even though we are running with the effective user ID root, our real user ID is still 60001, or nobody, and that's the user ID that Apache is running under.

It would be possible to modify the add_install_client script and remove this check. However, this is a risky proposition. A check that goes into this much painstaking detail was clearly implemented for a reason. We also do not want to modify scripts of this nature provided by Sun Microsystems and go outside what is officially supported.

If you wish to attempt to modify the add_install_client script on your own, you are welcome to try. However, you will almost definitely run into additional issues and have unpredictable results.

We can see from the first test runs that it is highly unlikely that our other test scenarios for this solution will succeed. They too will affect only the effective user ID rather than the real user ID. We will still attempt them for academic purposes.

Next, we will return the testuid.ksh script to its original permissions and turn the setuid bit on for the add_install_client script:

# chmod u-s /var/apache/cgi-bin/testuid.ksh
# chmod u+s /jumpstart/10/Solaris_10/Tools/add_install_client

Now launch a web browser and go to the http://10.1.1.1/cgi-bin/testuid.ksh. The results will be slightly different, but still a failure:

output of the id command:
uid=60001 (nobody) gid=60001 (nobody)

output of add_install_client:
You must be root to run /dev/fd/4

Contents of /etc/bootparams:

Since the id command is run by testuid.ksh, and we no longer have the setuid bit set on it, we expect to see that we no longer have an euid field in our output. The EUID will be set to root only when the actual add_install_client command is run. And we see that this has unpredictable results. We are still triggering the failure at the same if-then statement.

The /dev/fd/4 part of the output is unexpected, and it is a further indication that this method will not produce clean results.

Before we give up, we will attempt the same test, but have the setuid bit turned on for both the testuid.ksh script and the add_install_client script.

# chmod u+s /var/apache/cgi-bin/testuid.ksh

Now launch a web browser and go to the http://10.1.1.1/cgi-bin/testuid.ksh. As expected, we have another failure.

output of the id command:
uid=60001 (nobody) gid=60001 (nobody) euid=0 (root)

output of add_install_client:
You must be root to run /dev/fd/4

Contents of /etc/bootparams:

Again, we see that we have not accomplished our goal, and we have produced inherently unpredictable results. Before moving on to our next proposed solution, we should clean up by removing the setuid bit from both scripts.

# chmod u-s /var/apache/cgi-bin/testuid.ksh
# chmod u-s /jumpstart/10/Solaris_10/Tools/add_install_client

Use an RBAC Role With Root Execution Privileges

This solution will work but is a potential security hazard. It is, however, a step in the right direction.

For our next possible solution, we will turn to RBAC, a feature of the Solaris 9 and 10 OS that is often overlooked and considered cryptic by some. RBAC will allow us to create a type of user ID known as a role, which we can assign a wider range of permissions, including the ability to execute scripts and programs with an alternate real user ID.

The RBAC component is shipped with a pre-defined rights profile called Primary Administrator, which effectively has the same rights as the ID root. Once we create a new role and assign it the Primary Administrator rights profile, we should be able to run Apache using the user ID of the new role, but still have root-level execution permissions with a real user ID of root. Based on what we have seen so far, this should allow Apache to execute add_install_client.

RBAC also has an additional component called privileged shells. These are privileged versions of the standard UNIX shells we are already familiar with, for example, sh, ksh, csh. These new shells are effectively identical to the standard shells, but are capable of taking advantage of the extended execution permissions we will assign with RBAC. The privileged versions of the shells are named similarly to the standard shells: pfksh in place of ksh, pfsh in place of sh, and pfcsh in place of csh.

While this solution should produce the desired effect (a web server capable of running add_install_client), the solution poses potential security risks. By using the Primary Administrator rights profile, we are effectively just working around the fact that we cannot run Apache directly as root. All we will be doing here is running Apache as a user ID that has root permissions, so many of the same security issues might exist. This train of thought is still worth pursuing, however, because it will provide insights needed to produce a working solution that poses minimal security risks, as we will see later.

Our first task is to create the role that we will use to run Apache. The Solaris OS gives us the ability to use the roleadd and rolemod commands to set up roles. However, we can accomplish the same task by editing the /etc/passwd and /etc/shadow files. We will use the latter method here for educational purposes.

First, we will create a user ID to use as a role, and we will use an available UID number (99, in our example). We will use /jumpstart for a home directory and /bin/pfksh as the default shell. We will not have to assign a password to this role, because it is not actually intended for a user login. So we will specify that the password for this ID is locked. Since we are testing an inherently insecure solution, we will call this security_hole as a reminder.

To set this up, we can either use the appropriate roleadd and rolemod commands, or we can edit /etc/passwd and /etc/shadow directly.

# vi /etc/passwd

Add the following line:

security_hole:x:99:1::/jumpstart:/bin/pfksh

Edit /etc/shadow:

# vi /etc/shadow

Add the following line:

security_hole:*LK*:::::::

At this point, all we have done is create a standard user ID called security_hole to specify this ID as an RBAC role with Primary Administrator rights. We need to edit the /etc/user_attr file to assign our extended permissions.

# vi /etc/user_attr

Add the following line:

security_hole::::type=role;profiles=Primary Administrator

This defines the security_hole user ID as a role with Primary Administrator permissions. Exactly what permissions are assigned to the Primary Administrator profile? We can determine this by looking in the /etc/security/exec_attr file, which defines the execution attributes assigned to a rights profile.

# grep Primary /etc/security/exec_attr

Primary Administrator:suser:cmd:::*:uid=0;gid=0

If we examine the previous output, we see that Primary Administrator has permission to execute any program (specified by the * in the second-to-last field) with the real user ID 0 (root) and the real group ID 0 (root). As stated previously, this basically gives our security_hole ID the same level of permissions as the root account.

Now that our role is configured, and it has additional rights assigned, we can attempt to run Apache as the security_hole ID and test our testuid.ksh script.

First, stop Apache:

# /etc/init.d/apache stop

Change httpd.conf to run Apache as the security_hole ID:

# vi /etc/apache/httpd.conf

Change the following line from this:

User nobody

To this:

User security_hole

Then start Apache again:

# /etc/init.d/apache start

If we have done our job correctly, we should see better results when we test our script. Launch a web browser, point it to http://10.1.1.1/cgi-bin/testuid.ksh, and observe the results:

output of the id command:
uid=99(security_hole) gid=60001(nobody)

output of add_install_client:
You must be root to run
/jumpstart/10/Solaris_10/Tools/add_install_client

Contents of /etc/bootparams:

Despite our work, we have still failed. This is where the use of the privileged shells becomes important. Even though we specified /bin/pfksh as the default shell for the security_hole ID, our testuid.ksh script still explicitly calls /bin/ksh, which does not have the capability to execute programs with the permissions specified through RBAC. To take advantage of the new permissions we set up, we need to make a small adjustment to our script.

# vi /var/apache/cgi-bin/testuid.ksh

Change the following line from this:

#!/bin/ksh

To this:

#!/bin/pfksh

Now we have explicitly defined our script to run using the privileged version of ksh. This should allow our script to run add_install_client as UID 0 (root).

Launch a web browser, point it at http://10.1.1.1/cgi-bin/testuid.ksh, and observe the results:

output of the id command:
uid=0(root) gid=0(root)

output of add_install_client:
making /tftpboot
updating /etc/bootparams
copying boot file to /tftpboot/inetboot.SUNW4U.Solaris_10-1

Contents of /etc/bootparams:
sample_client root=jump:/jumpstart/10/Solaris_10/Tools/Boot
install=jump:/jumpstart/10
boottype=:in sysid_config=10.1.1.1:/jumpstart/sysid
install_config=10.1.1.1:/jumpstart/config rootopts=:rsize=8192

This is the output we have been looking for. Note that even though we are running Apache as the security_hole user, the id command now returns root for both the UID and GID, indicating that the pfksh shell is executing commands according to the permissions listed in /etc/security/exec_attr.

Since we are actually executing add_install_client as the real user ID root, we are successful. This solution might be useful; however, it does not address security concerns. A minor change to the testuid.ksh script could have disastrous results.

A simple typo when modifying the script, or testing new scripts to be run by the web server that have unresolved bugs, results in the full force of root-level permissions. Inadvertently adding the line rm -r /* to the testuid.ksh script and then pointing a web browser at it effectively destroys the system entirely. While this is an extreme example of the type of problems that can occur, it highlights the need to retain root execution ability for the commands we need to run as root (add_install_client and rm_install_client) and restricting root-level execution to those commands only. As we will see in the next section, RBAC does provide us with the functionality to do just that.

Before continuing, we will want to clean up.

Shut down Apache:

# /etc/init.d/apache stop

Change the user ID listed in httpd.conf back to nobody:

# vi /etc/apache/httpd.conf

Change the User directive to the following:

User nobody

Restart Apache:

# /etc/init.d/apache start

Delete the security_hole ID from /etc/passwd, /etc/shadow, and /etc/user_attr:

# vi /etc/passwd
# vi /etc/shadow
# vi /etc/user_attr

Delete any lines starting with security_hole in the previous files.

Change the shell directive in testuid.ksh back to /bin/ksh:

# vi /var/apache/cgi-bin/testuid.ksh

Change the following line from this:

#!/bin/pfksh

To this:

#!/bin/ksh

To clean up the JumpStart client we just configured, we'll want to run rm_install_client so we can start fresh again with our final solution.

# /jumpstart/10/Solaris_10/Tools/rm_install_client sample_client

removing sample_client from bootparams
removing /etc/bootparams, since it is empty
removing /tftpboot/inetboot.SUN4U.Solaris_10-1
removing /tftpboot

A Workable Solution That Poses Minimal Security Risks

So far, we have explored possible solutions that might have seemed to be workable approaches, but later proved otherwise, and we've found an approach that works, but needs some fine-tuning before it can be considered usable in most real working environments.

To refine our solution so that we are not opening up permissions to the web server more than necessary, we will take a closer look at RBAC rights profiles. Once we have a better understanding of the components involved, we should be able to create a rights profile that will allow only root UID execution for the core JumpStart scripts that we need in order to build a web-based user interface for our JumpStart server.

RBAC uses four configuration files, in addition to the /etc/passwd and /etc/shadow files, to define user IDs and roles and to assign additional rights to them. The /etc/user_attr file defines extended user attributes, and it lists associations between users or roles and rights profiles and authorizations.

The /etc/security/prof_attr file defines rights profiles, and it can be used to associate authorizations with them. The /etc/security/auth_attr file is used to define authorizations, which are pre-defined sets of permissions that are used by certain pieces of privileged software. This configuration file will not actually come into play in our solution.

The /etc/security/exec_attr file is the key component of our solution. This file can associate specific execution privileges with a rights profile.

With this very basic overview in mind, we can see that we will need to define a rights profile intended to grant permissions needed to run the core JumpStart scripts. We will need to associate execution permissions with that profile, and then build a role and associate it with our new rights profile.

The key to this process will be identifying what scripts or commands require privileged execution. We will start with the two core JumpStart scripts (add_install_client and rm_install_client) and see if there are any additional components that need permissions added.

Our first task will be to define a rights profile that will be able to execute the scripts needed for root privileges for the JumpStart server. We will name this profile JumpStart User. We will not be adding any authorizations to this profile, only execution permissions, so our initial profile definition in prof_attr will be very minimal.

# vi /etc/security/prof_attr

Add the following line:

JumpStart User:::Able to add and remove jumpstart clients:

All we are doing is telling RBAC that we have a profile called JumpStart User and assigning a comment field stating the purpose of the profile. Even though we are not using any authorizations here, we still need to make this minimal entry in prof_attr in order to define the profile.

The heart of this solution is contained in the changes we will make to the exec_attr file. Here we will make entries that will define the execution privileges we want the profile to have. We will create a line for each additional privilege we want. In our case, we want to be able to run the add_install_client script and the rm_install_client script, each with the real user ID root. This will require two lines in the exec_attr file.

# vi /etc/security/exec_attr

Add the following lines:

JumpStart User:suser:cmd:: \
:/jumpstart/10/Tools/add_install_client:uid=0
JumpStart User:suser:cmd:: \
:/jumpstart/10/Tools/rm_install_client:uid=0

The first field defines the profile that will be granted the defined privilege. The second and third fields, suser and cmd, define the security policy and the type of entity defined. As of the Solaris 9 OS, the only valid values for these fields are suser (superuser) and cmd (command). The Solaris 10 OS adds additional options for these fields; however, they are outside the scope of our current needs.

The next two fields are reserved for future features (which have not yet been implemented, even in the Solaris 10 OS). The sixth field identifies the entity that will be matched with the privilege attributes. This entity is usually the file name for a command, but may also be a directory path and may even contain wildcard (*) characters so multiple entities can be defined in one line. The last field is where we define the exact privilege attributes we will assign.

As seen previously, we are defining that the add_install_client and rm_install_client scripts will be run using a real user ID of 0 (root) if executed by a user that we grant JumpStart User rights to.

Now that we have defined our execution profile, we can create the user ID that we will run Apache as, and define it as a role that we have granted the User execution profile.

First, we will need to create a user ID jumpweb. We will use the directory /jumpstart as a home directory, use the privileged version of ksh (pfksh) as the default shell. We will need to choose a user ID number that is not currently in use. For our example, we will use the UID number 100. Since we do not intend this ID to be used for a user login, we can set the password to *LK* (locked).

First edit /etc/passwd:

# vi /etc/passwd

Add the following line:

jumpweb:x:100:1:/jumpstart:/bin/pfksh

Then edit /etc/shadow:

# vi /etc/shadow

Add the following line:

jumpweb:*LK*:::::::

The next task is to define our new user ID as a role and assign the User execution profile. This is a simple matter of creating an entry in /etc/user_attr.

# vi /etc/user_attr

Add the following line:

jumpweb::::type=role;profiles=JumpStart User

At this point, our user ID and RBAC configuration is complete. We can now reconfigure Apache to run as the user jumpweb, restart Apache, and test our configuration.

First, change the User directive in httpd.conf:

# vi /etc/apache/httpd.conf

Change the following line from this:

User nobody

To this:

User jumpweb

Restart the web server processes:

# /etc/init.d/apache stop

# /etc/init.d/apache start

Finally, we will need to make sure our test script is using the pfksh shell:

# vi /var/apache/cgi-bin/testuid.ksh

Change the following from this:

#!/bin/ksh

To this:

#!/bin/pfksh

We are now ready to test our solution. If we have done our job correctly, the add_install_client process should complete successfully. Launch a web browser, point it at http://10.1.1.1/cgi-bin/testuid.ksh, and observe the results:

output of the id command:
uid=100(jumpweb) gid=1(other)

output of add_install_client:
making /tftpboot
updating /etc/bootparams
copying boot file to /tftpboot/inetboot.SUNW4U.Solaris_10-1

Contents of /etc/bootparams:
sample_client root=jump:/jumpstart/10/Solaris_10/Tools/Boot
install=jump:/jumpstart/10
boottype=:in sysid_config=10.1.1.1:/jumpstart/sysid
install_config=10.1.1.1:/jumpstart/config rootopts=:rsize=8192

We will also want to verify that the jumpweb user is unable to run commands other than add_install_client and rm_install_client as root. Even though the password for jumpweb is listed as locked (*LK* in /etc/shadow), we can still use su to become superuser for the account if we start as root and run commands and scripts from the command line.

With this in mind, we can try out a few standard commands that are restricted to only root. Two commands that can easily demonstrate this are format and ifconfig.

The /usr/sbin/format command is meant to run only as root to prevent non-root users from accidentally reformatting disks that contain valid data. The /sbin/ifconfig command is still usable by non-root users, but it has many functions restricted to root. Non-root users only have the ability to view or list the status of the network interfaces, but only the root user is allowed to change settings and use the plumb, unplumb, down, and up options.

We can demonstrate that our jumpweb user ID is treated as a non-root user for these commands by starting as the root user, and using su to change to the jumpweb user:

# su - jumpweb

You should now see the prompt for the jumpweb user ID, and you should be in the home directory we defined for jumpweb.

$ pwd
/jumpstart

Now we can attempt to run the format command and see the results. If we were using the root account, we will see a list of available disk devices. The behavior, in this case, is different:

$ /usr/sbin/format
Searching for disks...done
No permission (or no disks found)!

$

Since jumpweb does not have the ability to run format as root, the command is unable to view the system's disk devices. This helps show that while we have root functionality for the commands we need to perform our JumpStart operations, we do not have complete root permissions, which greatly reduces the security exposure of allowing root access through Apache.

We can also see similar results with ifconfig. If run with the -a option, the command will run normally to allow a non-root user to view the settings and status of our network interfaces, but if we try to use the down option on the interface, the command will fail:

$ /sbin/ifconfig -a
lo0: flags=1000843<UP,LOOPBACK,RUNNING,MULTICAST,IPv4,VIRTUAL>
mtu 8232 index 1
inet 127.0.0.1 netmask ff000000
eri0: flags=1000843<UP,BROADCAST,RUNNING,MULTICAST,IPv4>
mtu 1500 index 2
inet 10.1.1.1 netmask ffffff00 broadcast 10.1.1.255
$

Note that even though we can run ifconfig with the -a option, it does not display the ether parameter to prevent a non-root user from discovering the hardware Ethernet address of the interface.

Now, if we try to bring an interface to the DOWN state, we will see very different results:

$ /sbin/ifconfig eri0 down
ifconfig: setifflags: SIOCSLIFFLAGS: eri0: permission denied
$

This is the behavior we should see if we use ifconfig as a non-root user. This prevents users other than root from inadvertently disabling a required network interface.

Conclusion

We have demonstrated that we can successfully configure our system so that the web server can run the commands we desire as root, and only those commands. This significantly reduces the security exposure to acceptable levels in an environment where your web server might be accessible from outside of its local network.

This meets the functional requirements we've been aiming for (the ability to code a browser-based user interface for a JumpStart server) and addresses the security concerns of allowing a web application to execute commands with root permission. Even so, applying this model should be done with caution and should be thoroughly tested before being implemented.

While this document has restricted its scope to the key functions necessary for a JumpStart server, the concepts can be easily applied to other applications if you keep in mind that there might be additional security concerns and exposures than what we have covered here.

About the Author

Vincent has worked with the Solaris OS since 1994. He has worn a variety of job titles and functions, including tape operator, help desk guy, backup administrator, hardware technician, system administrator, and disaster recovery technician. Currently, he is primarily responsible for providing support for, and developing infrastructure in support of, disaster recovery exercises. He also works on a wide range of minor research projects and scripting projects, as well as writing documentation. His specialties include JumpStart, backup and recovery, enterprise-level backup solutions, server deployment, and taking care of his two young daughters.

The information and links on this page have been provided by a BigAdmin user. The submitter is solely responsible for such information and links. Sun is not responsible for the availability of external sites or resources, and does not endorse and is not responsible or liable for any content, advertising, products, or other materials on or available from such sites or resources. Sun will not be responsible or liable, directly or indirectly, for any actual or alleged damage or loss caused by or in connection with use of or reliance on the information posted here, or goods or services available on or through any external site or resource.
 
 

Comments (latest comments first)

Discuss and comment on this resource in the BigAdmin Wiki

Unless otherwise licensed, code in all technical manuals herein (including articles, FAQs, samples) is provided under this License.


BigAdmin