Most web pages don’t serve only simple static content, they typically process and display all sorts of different dynamically generated contents. This is usually done via scripting, whereby the web server will process and execute a script and the result will be displayed to the user.
While CGI (Common Gateway Interface) is fairly old it does allow the web server to execute various types of scripts. Scripts such as PHP, Perl or Python can be used for example, allowing us to combine the power of scripting into our web pages.
Note: In this example we are working with Apache 2.4 in CentOS 7, some steps may vary depending on your version of Apache and specific Linux distribution.
How Apache Uses CGI
To make use of CGI, mod_cgi is used which is part of the default Apache installation. By default the /etc/httpd/conf/httpd.conf file specifies “Include conf.modules.d/*.conf” which includes the file “/etc/httpd/conf.modules.d/01-cgi.conf” containing the contents “LoadModule cgi_module modules/mod_cgi.so” which basically tells Apache to use the CGI module out of the box.
The /etc/httpd/conf/httpd.conf file then specifies the ScriptAlias directive which states the directory that will contain executable CGI files for Apache to execute, as specified as below.
ScriptAlias /cgi-bin/ "/var/www/cgi-bin"
This configuration is part of a default Apache installation and the /var/www/cgi-bin directory will also be setup by default. This is how Apache knows that anything in /var/www/cgi-bin should be executed, for example http://domain.com/cgi-bin/test.pl. Scripts within this directory are executed by the Apache user, so Apache must have execute permissions on them.
Other scripts such as PHP will typically be executed anywhere via mod_php which is more flexible than having them all in the cgi-bin directory, this way any .php file will be executed by Apache in /var/www/*
The output of the script that Apache executes will generally be specified as HTML or some other format that the web browser is capable of actually understanding and using so that it can be displayed to the user. This can be done by specifying the MIME type within the script.
An Example CGI Script
Below is a simple Perl script that we want Apache to execute, this has been saved as /var/www/cgi-bin/test.pl. While this particular script only prints static content, it demonstrates how the contents of the script could be modified to display various dynamic contents depending on different variables.
#!/usr/bin/perl print "Content-type: text/html\n\n"; print "Testing";
The first line of this script specifies that it is a Perl script and will be executed by /usr/bin/perl, note that for this to work Perl will need to first be installed onto the web server via “yum install perl”.
The second line specifies the MIME type as text/html which will be useful for the browser to understand what it is receiving.
The third line simply prints the text “Testing” out to the user.
This Perl script will be executed by Apache when it is called, therefore the Apache user will need execute permissions on the file. If you created this file as root with a text editor by default it will likely have 644 permissions meaning that no user, group or other will be able to execute it so we need to set execute permissions.
chmod +x /var/www/cgi-bin/test.pl
This gives execute permissions to all users and groups on the server which is probably over kill and potentially a security vulnerability depending on the contents of the script to be executed, however for testing this will suffice.
That’s it, with default Apache settings if we now browse to http://domain.com/cgi-bin/test.pl and the text “Testing” should display which has been executed by Perl as the Apache user.
CGI Scripts and SELinux
While the default settings of Apache are already configured to allow CGI scripts and have allowed us to create and execute our test Perl script, it is also important to understand how SELinux works with CGI scripts.
By default the /var/www/cgi-bin directory has the context of “httpd_sys_script_exec_t” as shown below with ls -Z, where the -Z flag shows us the SELinux context.
[[email protected] ~]# ls -laZ /var/www drwxr-xr-x. root root system_u:object_r:httpd_sys_script_exec_t:s0 cgi-bin [[email protected] ~]# ls -laZ /var/www/cgi-bin/test.pl -rwxr-xr-x. root root unconfined_u:object_r:httpd_sys_script_exec_t:s0 /var/www/cgi-bin/test.pl
This same context will therefore be applied on any files that are created within the /var/www/cgi-bin directory, this is required for them to be executed correctly.
A file will have the SELinux context of the directory that it has been created within applied, therefore if we created our test.pl file within say /root/ for example it would not have “httpd_sys_script_exec_t” set, as shown below. Note that if we instead copied the file with the ‘cp’ command the SELinux context would be updated to the destination folder automatically.
[[email protected] ~]# ls -laZ test.pl -rwxr-xr-x. root root unconfined_u:object_r:admin_home_t:s0 test.pl [[email protected] ~]# mv test.pl /var/www/html/ [[email protected] ~]# ls -laZ /var/www/cgi-bin/test.pl -rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 /var/www/cgi-bin/test.pl
If this is the case upon loading http://domain.com/cgi-bin/test.pl you will receive a Forbidden error with a message saying you don’t have permission to access the file on the server. The error log file at /var/log/httpd/error_log will show a similar result as the log entry below.
[Sat Sep 19 03:53:15.866552 2015] [core:error] [pid 12457] (13)Permission denied: [client 192.168.1.15:56143] AH00035: access to /cgi-bin/test.pl denied (filesystem path '/var/www/cgi-bin/test.pl') because search permissions are missing on a component of the path
SELinux problems are logged in the /var/log/audit/audit.log file by default, if you take a look in here it may not make much sense to you due to the way SELinux events are logged. If you have the ‘setroubleshoot-server’ package installed you can use the sealert command as shown below to process this log file into human readable information along with suggestions on how to best fix the problem.
[[email protected] ~]# sealert -a /var/log/audit/audit.log SELinux is preventing /usr/sbin/httpd from getattr access on the file /var/www/cgi-bin/test.pl. ***** Plugin restorecon (99.5 confidence) suggests ************************ If you want to fix the label. /var/www/cgi-bin/test.pl default label should be httpd_sys_script_exec_t. Then you can run restorecon. Do # /sbin/restorecon -v /var/www/cgi-bin/test.pl
As shown the restorecon command that has been suggested will apply the default context of the /var/www/cgi-bin directory onto the /var/www/cgi-bin/test.pl file which is exactly what we need to run to fix the problem.
Additionally for CGI scripts to be run by Apache the SELinux boolean value of httpd_enable_cgi must also be on, which is the case by default as shown below.
[[email protected] ~]# getsebool -a | grep httpd_enable_cgi httpd_enable_cgi --> on
For the purposes of demonstration, say someone had changed this to off as below.
[[email protected] ~]# setsebool httpd_enable_cgi off [[email protected] ~]# getsebool -a | grep httpd_enable_cgi httpd_enable_cgi --> off
With the boolean set to off if you attempt to browse to http://domain.com/cgi-bin/test.pl you will receive an Internal Server Error message, with the corresponding error log messages in /var/log/httpd/error_log as per below.
[Sat Sep 19 03:56:46.931994 2015] [cgi:error] [pid 12454] [client 192.168.1.15:56164] AH01215: (13)Permission denied: exec of '/var/www/cgi-bin/test.pl' failed [Sat Sep 19 03:56:46.932267 2015] [cgi:error] [pid 12454] [client 192.168.1.15:56164] End of script output before headers: test.pl
Nothing will be logged in the audit.log file this time as the SELinux boolean has been completely disabled, so it is important to ensure that it is on when working with CGI scripts.
If you get stuck or have trouble remembering any of this, remember the httpd-manual package which can be installed and viewed at http://localhost/manual.
From the main page, simply select CGI: Dynamic Content for help on this topic.
Here we have created a CGI script with Perl which has been successfully executed by Apache and returned the correct result to the users browser, demonstrating that with a simple CGI script we can serve content processed by a script through Apache.
This post is part of our Red Hat Certified Engineer (RHCE) exam study guide series. For more RHCE related posts and information check out our full RHCE study guide.