Last updated: 13-05-2014 16:38
<< Back | HOME |
Question: Securely run a shell script from a web dynamic page
let's assume you have a php or other dynamic page, ran by an httpd server, that has to run some shell script whenever it is loaded, e.g.:
$command = '/usr/local/bin/maintenance_script.sh'; shell_exec($command);
How do you configure the SELinux on that system to make this work?
$ sudo apt-get install apache2 $ sudo apt-get install php5
$ sudo vi /var/www/myPHPproblem/index.php </HEAD> <BODY style="background-color: #E6FFB2; font-family:arial;color: black;"> <BR> <CENTER> <TABLE> <TR><TD> <IMG SRC="Graphics/fta.png" alt="Free Technology Academy logo"> <BR><BR> </TD></TR> <TR><TD> <H2>My PHP Problem</H2> <BR><BR> </TD></TR> <TR><TD> <P>Select what you want to see on my Server</p> <FORM action="" method="post"> <INPUT type="radio" name="myOption" value='net' />Network links, IP address and routes<BR> <INPUT type="radio" name="myOption" value='pkg' />Package list to be deinstalled<BR><BR> <INPUT type="submit" name="btn_submit" value="Select"> </FORM> </TD></TR> </TABLE> </CENTER> <?php /* The next section in the program is the PHP piece. The program is waiting for the user to select. When this event happens it will run a script for the output. */ $radio == ''; if(isset($_POST['btn_submit'])) { $radio = $_POST['myOption']; } if ( $radio == 'net' ) { $data=shell_exec("scripts/net.sh 2>&1"); echo "<CENTER><TABLE><TR><TD><PRE>$data</PRE></TD></TR></TABLE></CENTER>"; } elseif ( $radio == 'pkg' ) { $data=shell_exec("scripts/pkg.sh 2>&1"); echo "<CENTER><TABLE><TR><TD><PRE>$data</PRE></TD></TR></TABLE></CENTER>"; } else { echo "<CENTER>You made no selection</CENTER>"; } ?>
$ sudo mkdir /var/www/myPHPproblem/Graphics $ sudo cp ~/Grpahics/fta.png /var/www/myPHPproblem/Graphics/
$ sudo mkdir /var/www/myPHPproblem/Scripts $ sudo vi net.sh #!/bin/bash echo -e "<b>ip link show</b><br>" ip link show echo -e "<br><b>ip addr list</b><br>" ip addr list echo -e "<br><b>ip route list</b><br>" ip route list
$ sudo vi pkg.sh #!/bin/bash echo -e "<br><b>Package list to be deinstalled</b><br>" dpkg --get-selections |grep deinstall
Change the ownership of all the files to the Apache2 user www-data and group www-data.
$ sudo chown -R www-data:www-data /var/www/myPHPproblem
Lets assume the two scripts will only work if owned by root. Change the ownership of the scripts directory and its files to root.
$ sudo chown -R root:root /var/www/myPHPproblem/scripts
Confirm the change.
$ ls -la /var/www/myPHPproblem/ total 24 drwxr-xr-x 4 www-data www-data 4096 May 11 10:41 . drwxr-xr-x 6 root root 4096 May 11 08:02 .. drwxr-xr-x 2 www-data www-data 4096 May 11 10:01 Graphics -rwxr-xr-x 1 www-data www-data 1280 May 11 10:36 index.php drwxr-xr-x 2 root root 4096 May 11 10:10 scripts $ ls -la /var/www/myPHPproblem/scripts/ total 16 drwxr-xr-x 2 root root 4096 May 11 10:10 . drwxr-xr-x 4 www-data www-data 4096 May 11 10:41 .. -r-x------ 1 root root 166 May 11 10:06 net.sh -r-x------ 1 root root 107 May 11 10:10 pkg.sh
http://fta.obriain.com/myPHPproblem
Change the shell_exec command to sudo the scripts.
$ vi index.php
Change the following lines as shown:
$data=shell_exec("scripts/net.sh 2>&1"); --> $data=shell_exec("sudo scripts/net.sh 2>&1"); $data=shell_exec("scripts/pkg.sh 2>&1"); --> $data=shell_exec("sudo scripts/pkg.sh 2>&1");
http://fta.obriain.com/myPHPproblem
Problem, the Apache user www-data has no rights to sudo and where would one enter the password ?
The default /etc/sudoers file on the server includes the directive:
#includedir /etc/sudoers.d
This causes sudo to read and parse any files in /etc/sudoers.d.
Add a file with an entry for each of the two scripts. This allows the user www-data, the Apache2 user to execute the two scripts as if it were the root user without a password. Rights for this file should be 0440.
$ sudo vi /etc/sudoers.d/apache www-data ALL = (root) NOPASSWD: /var/www/myPHPproblem/scripts/ $ sudo chmod 0440 /etc/sudoers.d/apache
http://fta.obriain.com/myPHPproblem
This is something to be avoided where possible, particularly if the webpage is calling scripts that are owned by root. In this example the scripts don't really need root privileges but the fact that they are owned by root without group or other rights demonstrate the point. It is also essential that the scripts directory has access limited to the root user only so scripts can't be accessed by the browser.
http://fta.obriain.com/myPHPproblem/scripts/net.sh
You don't have permission to access /myPHPproblem/scripts/net.sh on this server.
Apache/2.2.22 (Debian) Server at fta.obriain.com Port 80
Now Debian Server does not have SELinux enabled by default, but CentOS does. This adds an additional layer of complexity. This section explores migrating the test over from Debian to CentOS and dealing with the SELinux complexities.
$ sudo yum install httpd $ sudo yum install php
$ sudo vi /etc/httpd/conf/httpd.conf #ServerName www.example.com:80 --> ServerName centos.obriain.com:80 #Listen 12.34.56.78:80 --> Listen 109.106.96.157:80 $ sudo chkconfig httpd on $ sudo vi /etc/sysconfig/iptables (add in these two lines before any REJECT lines) -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT $ sudo service iptables restart iptables: Setting chains to policy ACCEPT: filter [ **OK** ] iptables: Flushing firewall rules: [ **OK** ] iptables: Unloading modules: [ **OK** ] iptables: Applying firewall rules: [ **OK** ] $ sudo service httpd start Starting httpd: httpd: Could not reliably determine the server's fully qualified domain name, using CENTOS.OBRIAIN.COM for ServerName [ **OK** ]
$ mkdir /var/www/html/myPHPproblem/ $ sudo cp ~/index.php /var/www/myPHPproblem/index.php $ mkdir /var/www/html/myPHPproblem/Graphics $ sudo cp ~/Grpahics/fta.png /var/www/myPHPproblem/Graphics/ $ mkdir /var/www/html/myPHPproblem/scripts $ sudo cp ~/scripts/* /var/www/html/myPHPproblem/scripts/* $ sudo chown -R apache:apache /var/www/html/myPHPproblem $ sudo chown -R root:root /var/www/html/myPHPproblem/scripts
Confirm the changes.
$ ls -la /var/www/html/myPHPproblem/ total 24 drwxr-xr-x. 4 apache apache 4096 May 13 05:23 . drwxr-xr-x. 3 apache apache 4096 May 13 05:42 .. drwxr-xr-x. 2 apache apache 4096 May 13 05:23 Graphics -rwxr-xr-x. 1 apache apache 1280 May 13 05:22 index.php drwxr-xr-x. 2 apache apache 4096 May 13 05:23 scripts $ ls -la /var/www/html/myPHPproblem/scripts/ total 16 drwxr-xr-x. 2 root root 4096 May 13 05:23 . drwxr-xr-x. 4 apache apache 4096 May 13 05:23 .. -rwx------. 1 root root 166 May 13 05:23 net.sh -rwx------. 1 root root 107 May 13 05:23 pkg.sh
Adjust the pkg.sh script for RPM.
$ vi /var/www/html/myPHPproblem/scripts/pkg.sh #!/bin/bash echo -e "<br><b>Package list that are not normal</b><br>" rpm -qas |grep -e "!^normal"
The default /etc/sudoers file on the server includes the directive:
#includedir /etc/sudoers.d
This causes sudo to read and parse any files in /etc/sudoers.d.
Add a file with an entry for each of the two scripts. This allows the user apache, the Apache2 user to execute the two scripts as if it were the root user without a password. Rights for this file should be 0440.
$ vi /etc/sudoers.d/apache Defaults:apache !requiretty apache ALL = (root) NOPASSWD: /var/www/html/myPHPproblem/scripts/ $ sudo chmod 0440 /etc/sudoers.d/apache apache:x:48:48:Apache:/var/www:/sbin/nologin --> apache:x:48:48:Apache:/var/www:/bin/sh
$ sudo vi /etc/selinux/config (change line) SELINUX=enforcing --> SELINUX=disabled $ sudo init 6
Test without SELinux.
$ getenforce Disabled
http://centos.obriain.com/myPHPproblem/
Test OK
Reenable SELinux.
$ sudo vi /etc/selinux/config (change line) SELINUX=disabled --> SELINUX=enforcing $ sudo init 6
To get the SELinux management utilities.
$ yum -y install policycoreutils-python
Enable httpd built-in scripting in SELinux.
$ sudo setsebool -P httpd_builtin_scripting 1
Label the scripts with httpd_sys_script_exec_t.
$ sudo semanage fcontext -a -t httpd_sys_script_exec_t /var/www/html/myPHPproblem/scripts/pkg.sh $ sudo restorecon -v /var/www/html/myPHPproblem/scripts/pkg.sh restorecon reset /var/www/html/myPHPproblem/scripts/pkg.sh context unconfined_u:object_r:httpd_sys_content_t:s0->unconfined_u:object_r:httpd_sys_script_exec_t:s0 $ sudo semanage fcontext -a -t httpd_sys_script_exec_t /var/www/html/myPHPproblem/scripts/pkg.sh $ sudo restorecon -v /var/www/html/myPHPproblem/scripts/pkg.sh restorecon reset /var/www/html/myPHPproblem/scripts/pkg.sh context unconfined_u:object_r:httpd_sys_content_t:s0->unconfined_u:object_r:httpd_sys_script_exec_t:s0 $ ls -lZ /var/www/html/myPHPproblem/scripts/ -rwx------. root root unconfined_u:object_r:httpd_sys_script_exec_t:s0 net.sh -rwx------. root root unconfined_u:object_r:httpd_sys_script_exec_t:s0 pkg.sh
http://centos.obriain.com/myPHPproblem/
sudo: unable to open audit system: Permission denied
Scripts still will not work.
$ getenforce Enforcing $ ps auxZ | grep httpd unconfined_u:system_r:httpd_t:s0 root 11100 0.1 1.6 233176 8124 ? Ss 07:39 0:00 /usr/sbin/httpd unconfined_u:system_r:httpd_t:s0 apache 11102 0.0 1.0 233176 5392 ? S 07:39 0:00 /usr/sbin/httpd unconfined_u:system_r:httpd_t:s0 apache 11103 0.0 1.2 233176 6152 ? S 07:39 0:00 /usr/sbin/httpd unconfined_u:system_r:httpd_t:s0 apache 11104 0.0 1.2 233176 6152 ? S 07:39 0:00 /usr/sbin/httpd unconfined_u:system_r:httpd_t:s0 apache 11105 0.0 1.2 233176 6152 ? S 07:39 0:00 /usr/sbin/httpd unconfined_u:system_r:httpd_t:s0 apache 11106 0.0 0.9 233176 4776 ? S 07:39 0:00 /usr/sbin/httpd unconfined_u:system_r:httpd_t:s0 apache 11107 0.0 0.9 233176 4776 ? S 07:39 0:00 /usr/sbin/httpd unconfined_u:system_r:httpd_t:s0 apache 11108 0.0 0.9 233176 4776 ? S 07:39 0:00 /usr/sbin/httpd unconfined_u:system_r:httpd_t:s0 apache 11109 0.0 0.9 233176 4776 ? S 07:39 0:00 /usr/sbin/httpd unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 dobriain 11118 0.0 0.1 103248 836 pts/0 S+ 07:40 0:00 grep httpd $ getsebool -a | grep httpd allow_httpd_anon_write --> off allow_httpd_mod_auth_ntlm_winbind --> off allow_httpd_mod_auth_pam --> off allow_httpd_sys_script_anon_write --> off httpd_builtin_scripting --> on httpd_can_check_spam --> off httpd_can_network_connect --> off httpd_can_network_connect_cobbler --> off httpd_can_network_connect_db --> off httpd_can_network_memcache --> off httpd_can_network_relay --> off httpd_can_sendmail --> off httpd_dbus_avahi --> on httpd_enable_cgi --> on httpd_enable_ftp_server --> off httpd_enable_homedirs --> off httpd_execmem --> off httpd_manage_ipa --> off httpd_read_user_content --> off httpd_run_stickshift --> off httpd_serve_cobbler_files --> off httpd_setrlimit --> off httpd_ssi_exec --> off httpd_tmp_exec --> off httpd_tty_comm --> on httpd_unified --> on httpd_use_cifs --> off httpd_use_fusefs --> off httpd_use_gpg --> off httpd_use_nfs --> off httpd_use_openstack --> off httpd_verify_dns --> off
Check the audit log for httpd denied entries.
$ sudo cat /var/log/audit/audit.log | grep httpd | tail -4 type=AVC msg=audit(1399992726.860:2412): avc: denied { setattr } for pid=1874 comm="sudo" scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:system_r:httpd_t:s0 tclass=key type=SYSCALL msg=audit(1399992726.860:2412): arch=c000003e syscall=250 success=no exit=-13 a0=3 a1=2e14c441 a2=7f79087cb675 a3=ca items=0 ppid=1873 pid=1874 auid=4294967295 uid=0 gid=48 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="sudo" exe="/usr/bin/sudo" subj=system_u:system_r:httpd_t:s0 key=(null) type=USER_END msg=audit(1399992726.861:2413): user pid=1874 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:httpd_t:s0 msg='op=PAM:session_close acct="root" exe="/usr/bin/sudo" hostname=? addr=? terminal=? res=success' type=CRED_DISP msg=audit(1399992726.861:2414): user pid=1874 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:httpd_t:s0 msg='op=PAM:setcred acct="root" exe="/usr/bin/sudo" hostname=? addr=? terminal=? res=success'
Using the audit2why tool to translate the SELinux audit messages into a description of why the access was denied.
$ sudo cat /var/log/audit/audit.log | grep httpd | audit2why | tail -4 type=AVC msg=audit(1399992010.158:1946): avc: denied { create } for pid=1711 comm="sudo" scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:system_r:httpd_t:s0 tclass=netlink_audit_socket Was caused by: The boolean allow_httpd_mod_auth_pam was set incorrectly. Description: Allow Apache to use mod_auth_pam Allow access by executing: # setsebool -P allow_httpd_mod_auth_pam 1
The mod_auth_pam is the Apache authentication module that implements Basic authentication on top of the Pluggable Authentication Module (PAM) library. Set the SELinux boolean value allow_httpd_mod_auth_pam to 1 (Enable).
$ sudo setsebool -P allow_httpd_mod_auth_pam 1
$ getenforce Enforcing
http://centos.obriain.com/myPHPproblem/
Test OK
<< Back | HOME |