Bug 45959

Summary: SSI include ignores SymlinkIfOwnerMatch directive
Product: Apache httpd-2 Reporter: Paul B. Henson <henson>
Component: mod_includeAssignee: Apache HTTPD Bugs Mailing List <bugs>
Status: RESOLVED FIXED    
Severity: normal CC: dlandin, johannes+bugs, mikevs
Priority: P2 Keywords: FixedInTrunk
Version: 2.2.8   
Target Milestone: ---   
Hardware: Sun   
OS: Solaris   
Attachments: Patch to disable ap_directory_walk cache

Description Paul B. Henson 2008-10-06 18:56:00 UTC
I'm running Apache 2.2.8, configured with SymlinkIfOwnerMatch and
server-side includes enabled.

It looks like the server-side include "include" directive ignores the
setting of SymlinkIfOwnerMatch?

For example, let's say I have an htpasswd configuration file outside of
the document root:

-rw-r-----   1 root     webservd       7 Oct  3 14:00
/usr/pkg/etc/httpd/htpasswd

If I then make a symbolic link to that from a user account:

lrwxrwxrwx   1 henson   csupomona      27 Oct  3 14:01
/user/henson/www/pass.html -> /usr/pkg/etc/httpd/htpasswd


Access is forbidden, with the following message in the log file:

[Fri Oct 03 14:01:51 2008] [error] [client 134.71.248.12] Symbolic link
not
allowed or link target not accessible: /export/user/henson/www/pass.html


However, if I create a server parsed HTML file in the same directory
containing the following:

        <!--#include file="pass.html" -->

When I request the .shtml file, the contents of the file pointed to by
the
symbolic link are included.

I had thought that configuring server side includes with IncludesNoExec
was reasonably safe, but it would appear that such a configuration allows
any file readable by the web server itself to be served?

I took a look at mod_include.c, the include directive appears to be handled
by the handle_include function which calls either ap_sub_req_lookup_file or
ap_sub_req_lookup_uri depending on whether the include is file or
virtual, and then calls ap_run_sub_req to presumably handle dumping out the
content of the include.

As a sub request, I would have intuitively thought it would honor the
configuration setting regarding symbolic links?

Am I confused? Is there something wrong with my configuration? Is this an
expected behavior (I searched quite a bit and didn't find anything
relevant)? I also posted to the mailing list last week:

http://marc.info/?l=apache-httpd-users&m=122306900916369&w=2

And didn't receive any responses that really answered my questions.

Thanks much for any help...
Comment 1 Paul B. Henson 2008-10-09 17:21:05 UTC
Just wondering if anyone had any thoughts on this? I'd like to verify if this is actually a bug, or expected behavior...

Thanks...
Comment 2 William A. Rowe Jr. 2008-10-09 17:45:16 UTC
Include cgi and include file do not validate their context.

Include virtual is what you were looking for, it does validate the cgi
or file resource included virtually.
Comment 3 Paul B. Henson 2008-10-09 18:01:12 UTC
Thanks for taking a look at this. However, include virtual appears to have the exact same problem. Given the following files in my web home directory:

lrwxrwxrwx 1 henson csupomona 27 Oct  3 14:01 pass.html -> /usr/pkg/etc/httpd/htpasswd
-rw-r--r-- 1 henson csupomona 37 Oct  9 17:50 test_ssi.shtml

If I attempt to access /~henson/pass.html,  I receive "Forbidden You don't have permission to access /~henson/pass.html on this server." as expected.

The contents of test_ssi.shtml are:

<!--#include virtual="pass.html" -->

When I access /~henson/test_ssi.shtml, the contents of /usr/pkg/etc/httpd/htpasswd appear in my browser.

As far as I can tell, "include virtual" also appears to ignore the setting of SymlinkIfOwnerMatch.

In addition, while you can enable includes without exec, I don't believe there is a way to allow include virtual only, IncludesNoExec allows both file and virtual includes. So even if include virtual respected SymlinkIfOwnerMatch (which it appears not to, unless I am missing something), it would not resolve the issue of being able to have SSI enabled on a server while preventing users from serving content via symbolic links.
Comment 4 William A. Rowe Jr. 2008-10-09 18:16:46 UTC
can you take a moment to ensure your browser cache is completely flushed out
and the server is restarted before trying /~henson/test_ssi.shtml once more?

Just want to rule out that you aren't looking at the prior request.
Comment 5 Paul B. Henson 2008-10-09 18:47:36 UTC
I'll do you one better than flushing my browser cache ;) :

-----
$ telnet 134.71.248.148 80
Trying 134.71.248.148...
Connected to 134.71.248.148.
Escape character is '^]'.
GET /~henson/test_ssi.shtml HTTP/1.1
Host: www.csupomona.edu

HTTP/1.1 200 OK
Date: Fri, 10 Oct 2008 01:28:09 GMT
Server: Apache/2.2.8 (Unix) mod_ssl/2.2.8 OpenSSL/0.9.8g
Accept-Ranges: bytes
Transfer-Encoding: chunked
Content-Type: text/html

7
secret
-----

"secret" is the contents of /usr/pkg/etc/httpd/htpasswd, I'm just using it as a test.

It's quite possible I am doing something stupid anyway, in which case I apologize in advance; but as far as I can tell everything is configured correctly.

Ideally I would like both virtual and file SSI includes to respect symbolic link restrictions, as I was counting on that for my deployment; the users will revolt if our new services don't include SSI, but my plan for deploying authenticated content will not be secure if any of them are able to create symlinks to and serve arbitrary web server readable content :(.

Thanks much...
Comment 6 Ruediger Pluem 2008-10-10 07:49:44 UTC
Please post your configuration.
Comment 7 Paul B. Henson 2008-10-10 19:28:32 UTC
I stripped the configuration down to the bare minimum to demonstrate the problem:

-------------------------------------
ServerRoot "/usr/pkg"
Listen 0.0.0.0:8080
LoadModule authz_host_module lib/httpd/mod_authz_host.so
LoadModule include_module lib/httpd/mod_include.so
LoadModule log_config_module lib/httpd/mod_log_config.so
LoadModule mime_module lib/httpd/mod_mime.so
User webservd
Group webservd
ServerAdmin root@csupomona.edu
ServerName www.csupomona.edu:8080
UseCanonicalName On
DocumentRoot "/usr/pkg/share/httpd/htdocs"
PidFile "/var/run/httpd-test.pid"
LockFile "/var/log/httpd/accept-test.lock"
<Directory />
        Options -FollowSymLinks
        AllowOverride None
        Order deny,allow
        Deny from all
</Directory>
<Directory "/usr/pkg/share/httpd/htdocs">
        Options -FollowSymLinks
        AllowOverride None
        Order allow,deny
        Allow from all
</Directory>
LogLevel warn
ErrorLog "/var/log/httpd/error-test_log"
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
combined
CustomLog "/var/log/httpd/access-test_log" combined
AddOutputFilter INCLUDES .shtml
-------------------------------

The contents of /usr/pkg/share/httpd/htdocs are:

-rw-r--r--   1 root     root        2326 Jun 25 09:58 apache_pb.gif
-rw-r--r--   1 root     root        1385 Jun 25 09:58 apache_pb.png
-rw-r--r--   1 root     root        2410 Jun 25 09:58 apache_pb22.gif
-rw-r--r--   1 root     root        1502 Jun 25 09:58 apache_pb22.png
-rw-r--r--   1 root     root        2205 Jun 25 09:58 apache_pb22_ani.gif
-rw-r--r--   1 root     root          44 Jun 25 09:58 index.html
lrwxrwxrwx   1 root     root          32 Oct 10 17:01 secret.html ->
/usr/pkg/etc/httpd/htaccess-test
-rw-r--r--   1 root     root          39 Oct 10 16:55 test_ssi.shtml


The ownership/permissions of the file /usr/pkg/etc/httpd/htaccess-test are:

-rw-r-----   1 root     webservd      12 Oct 10 17:01/usr/pkg/etc/httpd/htaccess-test

(Note: in this case, the ownership of the actual file matches the ownership of the symbolic link, but I changed the test configuration to "Options -FollowSymLinks" so it should not be allowed regardless)

The contents of test_ssi.shtml are:

<!--#include virtual="secret.html" -->

To rule out any browser caching or idiosyncrasies, I tested by connecting directly to the web server via telnet.

Attempting to access the symbolic link directly failed as expected:

-----
# telnet 0 8080
Trying 0.0.0.0...
Connected to 0.
Escape character is '^]'.
GET /secret.html HTTP/1.1
Host: www.csupomona.edu

HTTP/1.1 403 Forbidden
Date: Sat, 11 Oct 2008 00:03:08 GMT
Server: Apache/2.2.8 (Unix)
Content-Length: 213
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access /secret.html
on this server.</p>
</body></html>
--------------

With the following in the access/error log:

127.0.0.1 - - [10/Oct/2008:17:03:08 -0700] "GET /secret.html HTTP/1.1" 403
213 "-" "-"

[Fri Oct 10 17:03:11 2008] [error] [client 127.0.0.1] Symbolic link not
allowed or link target not accessible:
/usr/pkg/share/httpd/htdocs/secret.html


However, accessing the SSI file did not result in any errors, and returned the contents of the file pointed to by the symbolic link:

------
# telnet 0 8080
Trying 0.0.0.0...
Connected to 0.
Escape character is '^]'.
GET /test_ssi.shtml HTTP/1.1
Host: www.csupomona.edu

HTTP/1.1 200 OK
Date: Sat, 11 Oct 2008 00:04:12 GMT
Server: Apache/2.2.8 (Unix)
Accept-Ranges: bytes
Transfer-Encoding: chunked
Content-Type: text/plain

c
secret data

------

There was nothing in the error log, and only the following in the access log:

127.0.0.1 - - [10/Oct/2008:17:04:12 -0700] "GET /test_ssi.shtml HTTP/1.1"
200 13 "-" "-"



Ideally, I would expect the server-side include code to follow the same configuration regarding symbolic links as accessing the links directly would.

Is this expected behavior? A bug? A problem with my configuration or misunderstanding on my part?

Thanks...
Comment 8 Paul B. Henson 2008-10-20 14:19:25 UTC
Any further thoughts on this?

Thanks...
Comment 9 Paul B. Henson 2008-10-29 19:35:52 UTC
I dug into this some more.

In the file request.c, the function ap_directory_walk contains the following code:

    /* If we have a file already matches the path of r->filename,
     * and the vhost's list of directory sections hasn't changed,
             * we can skip rewalking the directory_walk entries.
             */
            if (cache->cached
                && ((r->finfo.filetype == APR_REG)
                    || ((r->finfo.filetype == APR_DIR)
                        && (!r->path_info || !*r->path_info)))
                && (cache->dir_conf_tested == sec_ent)
                && (strcmp(entry_dir, cache->cached) == 0)) {
                /* Well this looks really familiar!  If our end-result (per_dir_result)
                 * didn't change, we have absolutely nothing to do :)
                 * Otherwise (as is the case with most
dir_merged/file_merged requests)
                 * we must merge our dir_conf_merged onto this new
r->per_dir_config.
                 */


                if (r->per_dir_config == cache->per_dir_result) {
                    return OK;
                }

                if (r->per_dir_config == cache->dir_conf_merged) {
                    r->per_dir_config = cache->per_dir_result;
                    return OK;
                }

When the SSI include is processed, either file or virtual, the check in the last if statement shown above is true, and the function immediately returns OK with no further processing.

If I comment out the section of code that checks for a cached entry, and force it to fully process the request, the attempted inclusion of the symbolic link fails:

[Wed Oct 29 19:30:50 2008] [error] [client 134.71.248.12] Symbolic link not
allowed or link target not accessible: /export/user/bldewolf/www/secrets3
[Wed Oct 29 19:30:50 2008] [error] [client 134.71.248.12] unable to include
"secrets3" in parsed file /export/user/bldewolf/www/test3.shtml


There appears to be an invalid assumption in this cache check, clearly behavior is different if the subrequest is fully processed rather than using the cache.

I think this is a security bug, the configured restriction on symbolic link handling is being bypassed by the cache optimization.

Please let me know what you think of this.



Comment 10 Paul B. Henson 2008-11-14 19:49:33 UTC
I've done some further testing, and confirmed that this is only a problem if the included symbolic link is in the same directory as the shtml file.

Consider the following file served via apache:

$ ls -l /export/user/henson/www/

total 2-rw-------+  1 henson   csupomona      13 Nov 14 19:24 secured.html

For the sake of discussion, assume this file is readable by the web server, but restricted to require authentication.

Now, another user creates a symbolic link to that file:

$ ls -l /export/user/astudent/www/symlink.html
lrwxrwxrwx 1 astudent csupomona 36 Nov 14 19:31 /export/user/astudent/www/symlink.html ->/export/user/henson/www/secured.html


Attempting to access the symbolic link directly fails, as SymlinkIfOwnerMatch is configured:

$ curl http://stan.unx.csupomona.edu/~astudent/symlink.html
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access /~astudent/symlink.html
on this server.</p>
</body></html>


[Fri Nov 14 19:29:06 2008] [error] [client 134.71.248.140] Symbolic link
not allowed or link target not accessible:
/export/user/astudent/www/symlink.html

Now, the user creates an SSI file:

$ ls -l /export/user/astudent/www/symlink_ssi.html

-rw-r--r--+ 1 astudent csupomona 40 Nov 14 19:27
/export/user/astudent/www/symlink_ssi.shtml

Whose contents are:

$cat /export/user/astudent/www/symlink_ssi.shtml
<!--#include virtual="/~astudent/symlink.html" -->

Accessing this file:

$ curl http://stan.unx.csupomona.edu/~astudent/symlink_ssi.shtml
Secret data.

Returns the restricted data, bypassing the SymlinkIfOwnerMatch configuration directive.

As I discovered, this appears to be a bug in ap_directory_walk. Let's say we move the SSI file to a subdirectory:

$ ls -l /export/user/astudent/www/subdir/symlink_ssi.shtml
-rw-r--r--+  1 astudent csupomona      40 Nov 14 19:27
/export/user/astudent/www/subdir/symlink_ssi.shtml


Attempting to request it then fails as expected:

$ curl http://stan.unx.csupomona.edu/~astudent/subdir/symlink_ssi.shtml
[an error occurred while processing this directive]

[Fri Nov 14 19:37:04 2008] [error] [client 134.71.248.140] Symbolic link
not allowed or link target not accessible:
/export/user/astudent/www/symlink.html
[Fri Nov 14 19:37:04 2008] [error] [client 134.71.248.140] unable to
include "/~astudent/symlink.html" in parsed file
/export/user/astudent/www/subdir/symlink_ssi.shtml


The exact same include behaves differently depending on whether or not the included file happens to be in the same directory as the SSI.

Again, this would appear to be a security bug to me. Not a critical one by any means, but still a security bug. I would greatly appreciate some feedback from a developer on this issue.
Comment 11 Nick Kew 2008-11-15 15:46:23 UTC
This looks like a duplicate of PR#41960, which was fixed in 2.2.9.
Please reopen if an upgrade doesn't fix it for you.

*** This bug has been marked as a duplicate of bug 41960 ***
Comment 12 Paul B. Henson 2008-11-16 09:50:43 UTC
My most recent testing was under 2.2.9:

[Fri Nov 14 19:04:27 2008] [notice] Apache/2.2.9 (Unix) mod_ssl/2.2.9 OpenSSL/0.9.8h configured -- resuming normal op
erations

I don't think they are related. The other bug was regarding ap_location_walk for subrequests that don't have an associated file. This bug appears to be in ap_directory_walk, and there is a file associated.
Comment 13 Paul B. Henson 2008-11-22 14:42:24 UTC
Created attachment 22915 [details]
Patch to disable ap_directory_walk cache

For now, I'm running with this patch which disables the caching in ap_directory_walk, and instead logs a notice that the cache would have been used. Seems to be working fine, solves my problem. Not sure what extra inefficiency this is, but I need security first.

Please let me know when you have time to investigate this problem and find a better resolution.
Comment 14 Nick Kew 2008-12-30 19:27:04 UTC
OK, I've just had a good hack at this, and I can reproduce the problem.  It seems "Options" values are completely ignored in subrequests, so it never tests for symlinks, let alone if owner matches.  That applies both to file and virtual/uri lookups.

Please bug me if I don't apply your patch or something equivalent within a week.  Thanks for persisting with the report!
Comment 15 Paul B. Henson 2009-01-06 14:48:45 UTC
Thanks, I appreciate the acknowledgment that this is a valid bug. All my patch does is disable the caching,  which might not be the best thing to do. I didn't have the time to familiarize myself with the code enough to see if there was a way to cache while still respecting options. If there isn't, then getting rid of the caching might be the only secure approach.
Comment 16 Bob Ionescu 2009-01-06 15:22:30 UTC
There's a propose for backporting patches which have been applied to trunk some time ago, see r732133
Comment 17 Paul B. Henson 2009-01-07 15:52:49 UTC
Based on my inexpert review, the referenced patch does look like it would resolve the problem. I look forward to its inclusion :).
Comment 18 Nick Kew 2009-01-12 06:50:25 UTC
Fix backported in r733754.
Comment 19 Nick Kew 2009-01-29 15:49:21 UTC
*** Bug 14206 has been marked as a duplicate of this bug. ***
Comment 20 Bob Ionescu 2009-04-16 08:06:13 UTC
*** Bug 47039 has been marked as a duplicate of this bug. ***
Comment 21 Bob Ionescu 2009-06-11 04:42:52 UTC
*** Bug 47337 has been marked as a duplicate of this bug. ***