Section 5 - Exercise 1 - Securely run a shell script from a web dynamic page

Diarmuid O'Briain, diarmuid@obriain.com
11-05-2014, version 1.0

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?


1. Debian Server

1.1. Install software

  $ sudo apt-get install apache2
  $ sudo apt-get install php5
  

1.2. Create PHP Script in /var/www/myPHPproblem/

  $ 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>";
  }
  
  ?>
  

1.3. Add Graphics folder and add image

  $ sudo mkdir /var/www/myPHPproblem/Graphics
  $ sudo cp ~/Grpahics/fta.png /var/www/myPHPproblem/Graphics/
  

1.4. Add a Scripts folder and create scripts

1.4.1. net.sh

  $ 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
  

1.4.2. pkg.sh

  $ sudo vi pkg.sh
  
  #!/bin/bash
  
  echo -e "<br><b>Package list to be deinstalled</b><br>"
  dpkg --get-selections |grep deinstall
  

1.5. Change ownerships

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
  

1.6. Test the server

http://fta.obriain.com/myPHPproblem

1.7. Change the php index file to sudo

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");        
  

1.8. Test the server again

http://fta.obriain.com/myPHPproblem

Problem, the Apache user www-data has no rights to sudo and where would one enter the password ?

1.9. Create file in /etc/sudoers.d

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
  

1.10. Try the test on the server again

http://fta.obriain.com/myPHPproblem

1.11. Summary

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

Forbidden

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

2. CentOS with SELinux

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.

2.1. Install software

  $ sudo yum install httpd
  $ sudo yum install php
  

2.2. Configure Apache Server

  $ 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**  ]
  

2.3. Copy files to webserver and set permissions

  $ 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
  

2.4. 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"
  

2.5. Allow the apache user execute the scripts

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
  

2.6. Disable SELinux and test

  $ 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

2.6.1. Renable SELinux

Reenable SELinux.

  $ sudo vi /etc/selinux/config 
  
  (change line)
  SELINUX=disabled   -->   SELINUX=enforcing
  
  $ sudo init 6
  

2.7. SELinux management utilities

To get the SELinux management utilities.

  $ yum -y install policycoreutils-python
  

2.8. Configure SELinux

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.

2.9. Troubleshooting

  $ 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
  

2.9.1. Further troubleshooting

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).

2.10. Allow Apache PAM Authentication

  $ sudo setsebool -P allow_httpd_mod_auth_pam 1
  

2.11. Test again

  $ getenforce
  Enforcing
  

http://centos.obriain.com/myPHPproblem/

Test OK


<< Back HOME