Bug 751404 - SELinux doesn't prevent Apache, PHP and other httpd_t labeled processes to access remote 80/tcp
Summary: SELinux doesn't prevent Apache, PHP and other httpd_t labeled processes to ac...
Keywords:
Status: CLOSED NOTABUG
Alias: None
Product: Red Hat Enterprise Linux 6
Classification: Red Hat
Component: selinux-policy
Version: 6.1
Hardware: x86_64
OS: Linux
unspecified
high
Target Milestone: rc
: ---
Assignee: Miroslav Grepl
QA Contact: BaseOS QE Security Team
URL:
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2011-11-04 15:43 UTC by Marko Uskokovic
Modified: 2011-12-09 09:56 UTC (History)
11 users (show)

Fixed In Version:
Doc Type: Bug Fix
Doc Text:
Clone Of:
Environment:
Last Closed: 2011-12-09 09:56:13 UTC
Target Upstream Version:


Attachments (Terms of Use)

Description Marko Uskokovic 2011-11-04 15:43:28 UTC
Description of problem:

Managing confined services guide said:
httpd_can_network_connect - When disabled, this Boolean prevents HTTP scripts and modules from initiating a connection to a network or remote port. Turn this Boolean on to allow this access."

If I understand that correctly, SELinux should prevent php scripts (running via mod_php) from opening remote urls with fopen function by default, that is when httpd_can_network_connect --> off

Here are the links that confirm that behavior in the past:
http://www.php.net/manual/en/function.fopen.php#56551
https://bugzilla.redhat.com/show_bug.cgi?id=164700

My problem is that Apache, fopen function of php running via mod_php and every program that php executes (like wget, so it runs in httpd_t context)  are all allowed access to remote machine's port 80/tcp.

SELinux IS preventing Apache, php and httpd_t programs from accessing eg. port 31254/tcp, but not port 80/tcp.


Version-Release number of selected component (if applicable):

[root@turncoat ~]# uname -a
Linux turncoat.ha.rs 2.6.32-131.17.1.el6.x86_64 #1 SMP Thu Sep 29 10:24:25 EDT 2011 x86_64 x86_64 x86_64 GNU/Linux

[root@turncoat ~]# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 6.1 (Santiago)

[root@turncoat ~]# semanage -o -
boolean -D
login -D
login -a -s unconfined_u -r 's0-s0:c0.c1023' __default__
login -a -s unconfined_u -r 's0-s0:c0.c1023' root
login -a -s system_u -r 's0-s0:c0.c1023' system_u
user -D
port -D
interface -D
node -D
fcontext -D

[root@turncoat ~]# getsebool -a|grep http
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_read_user_content --> 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_gpg --> off
httpd_use_nfs --> off

[root@turncoat ~]# sestatus
SELinux status:                 enabled
SELinuxfs mount:                /selinux
Current mode:                   enforcing
Mode from config file:          enforcing
Policy version:                 24
Policy from config file:        targeted

[root@turncoat ~]# rpm -qa|grep selinux
libselinux-2.0.94-5.el6.x86_64
selinux-policy-3.7.19-93.el6_1.7.noarch
selinux-policy-targeted-3.7.19-93.el6_1.7.noarch
libselinux-python-2.0.94-5.el6.x86_64
libselinux-utils-2.0.94-5.el6.x86_64


How reproducible:
Always 

Steps to Reproduce:
1. yum install httpd mod_php
2. create /var/www/html/marko1.php with:
<?php
$file = fopen ("http://www.example.com", "r");
if (!$file) {
    echo "<p>Unable to open remote file.\n";
    exit;
}
while (!feof ($file)) {
    $line = fgets ($file, 1024);
        echo ($line);
}
fclose($file);
?>

3. create /var/www/html/marko2.php with:
<?php
$file = fopen ("http://www.example.com:31254/", "r");
if (!$file) {
    echo "<p>Unable to open remote file.\n";
    exit;
}
while (!feof ($file)) {
    $line = fgets ($file, 1024);
        echo ($line);
}
fclose($file);
?>

4. create /var/www/html/marko3.php with:
<?php echo exec('whoami'); echo exec ('id -Z');
exec ('wget http://www.example.com -O /tmp/example.html');
exec ('wget http://www.example.com:31254 -O /tmp/example-31254.html');
?>


Actual results:

 - By opening in browser marko1.php, the html code from www.example.com is delivered (which is bad). No AVC's, no prevention of accessing remote machine's port 80/tcp and packets are actually sent over network.

 - By opening in browser marko2.php, the "Unable to open remote file." message is reported (which is good), and AVC message is reported:
type=AVC msg=audit(1320417644.481:140): avc:  denied  { name_connect } for  pid=1152 comm="httpd" dest=31254 scontext=unconfined_u:system_r:httpd_t:s0 tcontext=system_u:object_r:port_t:s0 tclass=tcp_socket

 - By opening in browser marko3.php, the /tmp/example.html file is created on server with content from http://www.example.com (which is bad). The /tmp/example-31254.html file is also created, but it is empty (which is good) as wget is prevented to access remote network with AVC message (which is good):
type=AVC msg=audit(1320417649.970:141): avc:  denied  { name_connect } for  pid=1306 comm="wget" dest=31254 scontext=unconfined_u:system_r:httpd_t:s0 tcontext=system_u:object_r:port_t:s0 tclass=tcp_socket


Expected results:
When disabled, httpd_can_network_connect boolean should prevent HTTP scripts, modules and programs running under httpd_t context from initiating a connection to a network or remote port, no matter what remote port is in question.

Additional info:
No proxy* modules are enabled in apache's httpd.conf (and the result is the same when they are) and I can verify that tcp packets have been sent over network as a result of accessing those php files in browser.

Comment 3 Daniel Walsh 2011-11-14 21:57:20 UTC
This is really not a bug, although we could do better. 


apachectl requires this access to shut down an apache server.  We probably could tighten it do be only local host.

Comment 4 Daniel Walsh 2011-11-14 21:59:36 UTC
Joe what do you think?

Comment 7 Joe Orton 2011-11-15 16:15:57 UTC
"apachectl requires this access to shut down an apache server"

Specifically, the requirement is that the httpd parent must be able to connect to port 80 on addresses bound to local interfaces by default.

Yes, it is certainly desirable that php scripts cannot make outgoing
connections to port 80 by default, that would help prevent e.g. canned attacks
against bad PHP webapps etc - the first step is often to wget some toolkit from a remote site.

Would it be possible for to have separate roles for httpd parent and children? 

Or else is there separation between making a TCP connection to the local host
vs a remote address which can be expressed in the rule?

(and is the distinction between "local" and "remote" more sophisticated than "::1 and 127.0.0.1" vs everything else?)

Comment 8 Daniel Walsh 2011-11-16 14:13:05 UTC
Joe I don't understand what you mean by parent and child?  Are there two different process here?


Or is apachectl connecting to the httpd service via port 80?

I would try this out on my F17 box but I have hosed it up trying to setup PrivateTmp for httpd.

Currently in F16 we are not allowing httpd_t to connect to port 80 by default, and I want to know if that breaks apachectl.

Comment 9 Joe Orton 2011-11-16 15:46:48 UTC
I am not sure why you keep refering to "apachectl" specifically.

httpd runs a monitoring process, the parent, and a bunch of child processes which handle connections.  The parent runs as root, the children all setuid() to uid/gid apache once forked, and only handle connections once running as that less-privileged user.

When the httpd init script, or systemd, or apachectl want to gracefully restart httpd to load a new configuration, it works as following:

a) the init script/apachectl/systemd send a signal to the httpd parent process
b) the parent process sends signals to all running child processes
c) the parent process sends dummy HTTP requests to address/port combinations on which it knows the children are listening.  This ensures they pop out of accept() if they are blocked there.
d) existing children all terminate, and new ones are started up using the new configuration

(c) is the requirement to be able to connect to port 80 on the local host by default.

Comment 10 Daniel Walsh 2011-11-16 21:35:34 UTC
Ok thanks for the explanation.

Of course httpd is not forking and execing so we can not use program transitions to run the parent as a different context then the children.

One option would be to add SELinux smarts to httpd to let it to a setexec()

httpd_parent_t -> fork() setcur(httpd_t)

Then just allow httpd_parent_t to connect to http_port_t by default.

Or we turn on labelling of the localhost nodes/ethernet and only allow httpd_t to connect send/recv packets over localhost ether net or notes, and allow httpd_t to connect to http_port_t, 

But I am not sure we can block that since we have to allow httpd_t to send/recv packets for DNS name resolution.


Note You need to log in before you can comment on or make changes to this bug.