You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@httpd.apache.org by Brian Candler <B....@pobox.com> on 2005/07/28 13:33:48 UTC

[users@httpd] shtml and suexec

I have a suexec-related question. I'm running (and looking at the source
code for) Apache 1.3.33.

I am mapping the Host: header to the filesystem path using mod_rewrite and a
dbm map, for mass virtual hosting. Since "User" can only be set statically
inside a <VirtualHost> container, it looks like I can't use Apache's suexec,
so I need something like cgiwrap which runs under the userid of the script
file itself.

However, this model seems to break down for .shtml pages which contain, say,
    <!--#exec cmd="foo"-->

Firstly, I need "foo" to run under the userid of the .shtml page (or the
website owner), not the userid of program "foo".

Secondly, shtml users expect their cmd to be run under a shell; however in
main/util_script.c, I see that if suexec is active, the shell is not run.

    if (ap_suexec_enabled
...
        if (shellcmd) {
            execle(SUEXEC_BIN, SUEXEC_BIN, execuser, grpname, argv0,
                   NULL, env);
        }

... but in the non-suexec case:

   else {
        if (shellcmd) {
            execle(SHELL_PATH, SHELL_PATH, "-c", argv0, NULL, env);
        }

So as far as I can tell <!--#exec cmd="echo hello"--> isn't going to work if
suexec is active, since in suexec, argv[3]="echo hello". I guess the wrapper
could run a shell, but there's no flag telling it to do so.

I am considering whether I need to write my own setuid wrapper, and pass the
site username in an environment variable: e.g.

RewriteMap      hostmap         dbm:/conf/hostmap

RewriteCond ${hostmap:${tolower:%{HTTP_HOST}}}    root=(/[^,]+),uid=(\d+)
RewriteRule ^(.*\.shtml) %1$1 [E=UID:%2]

This seems pretty hairy to me. Or perhaps the wrapper can look at
SCRIPT_FILENAME or PATH_TRANSLATED and stat() that file?

Anybody have any other suggestions?

A third party module which uses a .db or .cdb file to lookup the "Host:"
header and set docroot+UID+GID would be acceptable.

Otherwise, I guess what I really want is to be able to set [USER=...] in a
mod_rewrite rule, but looking at the docs for apache 2.0 and 2.1, I don't
think this feature has been added.

Regards,

Brian Candler.

---------------------------------------------------------------------
The official User-To-User support forum of the Apache HTTP Server Project.
See <URL:http://httpd.apache.org/userslist.html> for more info.
To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
   "   from the digest: users-digest-unsubscribe@httpd.apache.org
For additional commands, e-mail: users-help@httpd.apache.org


Re: [users@httpd] shtml and suexec

Posted by Brian Candler <B....@pobox.com>.
On Thu, Jul 28, 2005 at 10:28:09AM -0400, Dan Mahoney, System Admin wrote:
> On Thu, 28 Jul 2005, Brian Candler wrote:
> 
> If you don't have a shell, then your system runs /bin/echo, no?

No. Firstly, suexec calls execv(), which does not do a search in $PATH.
Secondly, "echo foo" is treated as a single argument, that is, it's not
split into "echo" and "foo". It would run a program called "echo foo" in the
current directory, if there were an executable file with that name.

I've done a bit of research (since the reasoning doesn't seem to be
documented), and I think I can see why this is the case.

Suppose you passed the shell to suexec like this:

    suexec brian brian /bin/sh -c echo foo
           ^^^^^ ^^^^^ ^^^^^^^ ^^ ^^^^^^^^
            user group  arg1  arg2  arg3

The current suexec policy would refuse to execute it for several reasons.
arg1 is an absolute path (it starts with /); /bin/sh is not under the Apache
document root; and /bin/sh is not owned by user 'brian'

So, suppose you modified suexec so that it accepted '/bin/sh' as a trusted
executable, and would run it with those arguments? The trouble is then that
anyone who breaks into the webserver (i.e. runs code as the webserver user)
can then automatically run any arbitrary command as any arbitrary user on
the system. e.g.

   suexec fred fred /bin/sh -c "sh -i"

would give them a nice interactive shell as that user.

The current suexec security model means that a user who breaks into the
system as the Apache user can only run scripts which already exist, AND
which are within the webroot, AND run them as the user who wrote the script.
In other words, they can more or less only run the same things as people
contacting the webserver from outside via HTTP can run. There may be holes
in the scripts themselves they can exploit to escalate privilege, but it
requires more work.

So, I'm pretty much resigned to setting IncludesNOEXEC.

A longer description I was typing as I was researching this is attached
below, in case it's of any use to anyone. I did have an idea for a better
solution, and that's attached too.

Cheers,

Brian.

PROBLEM DESCRIPTION
-------------------

The specification for shtml (server-parsed) files includes the feature for
command execution: <!--#exec cmd="echo foo"-->

In normal use, this is run under a shell [extract from main/util_script.c]:

        if (shellcmd) {
            execle(SHELL_PATH, SHELL_PATH, "-c", argv0, NULL, env);
        }

However, when suexec is active, it is not:

    if (ap_suexec_enabled
...
        if (shellcmd) {
            execle(SUEXEC_BIN, SUEXEC_BIN, execuser, grpname, argv0,
                   NULL, env);
        }

As a result, the semantics of #exec cmd="..." are changed, and scripts which
would work without suexec will break with it.

Even the simple examples given at http://httpd.apache.org/docs/1.3/howto/ssi.html
will fail:

    <pre>
    <!--#exec cmd="ls" -->
    </pre>

The user would have to work around it like this:

    <!--#exec cmd="foo.sh"-->

and create foo.sh containing

    #!/bin/sh
    ls

and set the +x bit.

POSSIBLE SOLUTION?
------------------

Pass the shell to suexec:

        if (shellcmd) {
            execle(SUEXEC_BIN, SUEXEC_BIN, execuser, grpname,
                   SHELL_PATH, "-c", argv0, NULL, env);
        }

However, the current suexec binary would reject this under its security
model. Specifically, it violates:

4.  Does the target program have an unsafe hierarchical reference?

    Does the target program contain a leading '/' or have a '..'
    backreference? These are not allowed; the target program must reside
    within the Apache webspace.

13. Is the directory within the Apache webspace?

    If the request is for a regular portion of the server, is the requested
    directory within the server's document root?

18. Is the target user/group the same as the program's user/group?

It would be possible to make a hardcoded exception which says that
SHELL_PATH can skip these tests. Would that be considered an unacceptable
security risk? It seems so.

The problem is that if you break into a webserver (i.e. become the webserver
owner), then this would allow you to run arbitary code as an arbitary user.
Without this feature, suexec would only let you run every CGI script in the
system as the script author (although on a typical system, there would be
plenty of holes in those scripts to exploit :-)

A STRONGER SOLUTION
-------------------

Process .shtml pages in an entirely separate CGI program, started by a
wrapper which sets userid to the owner of the .shtml page (a la cgiwrap).

This is a secure but heavyweight solution for pages which don't require
<!--exec cmd...-->. It could, however, be invoked only for pages which
contain that construct.

Does such a standalone CGI version of mod_includes already exist?

A MIDDLEWEIGHT SOLUTION?
------------------------

When Apache is scanning an SSI file and comes across #exec cmd, it could
invokes a suexec wrapper with:
- the name of the .shtml file
- an index (0, 1, 2...) for which #exec cmd instance is required.

The wrapper does the same checks as suexec: i.e. it checks that the .shtml
file and the directory containing it are owned by the target user, and that
it is within the Apache document root.

After switching to the target uid/gid, it then re-parses the .shtml file
looking for the n'th instance of
  <!--#exec cmd="...."-->
and executes it it using /bin/sh -c "...."

Alternatively, the wrapper could be passed an offset into the file. The
wrapper would check that the data at this offset starts <!--#exec cmd="...">
and reject if not.

I think this is reasonably secure; the chances of this sequence of
characters appearing in a file which is *not* a .shtml file are pretty low.
However if somebody creates a documentation file foo.txt within their
webspace, containing

   Here's a useful example:
   <!--#exec cmd="sh -i"-->

then someone running as the webserver user could use suexec to run it :-|

---------------------------------------------------------------------
The official User-To-User support forum of the Apache HTTP Server Project.
See <URL:http://httpd.apache.org/userslist.html> for more info.
To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
   "   from the digest: users-digest-unsubscribe@httpd.apache.org
For additional commands, e-mail: users-help@httpd.apache.org


Re: [users@httpd] shtml and suexec

Posted by "Dan Mahoney, System Admin" <da...@prime.gushi.org>.
On Thu, 28 Jul 2005, Brian Candler wrote:

If you don't have a shell, then your system runs /bin/echo, no?

However, shell environment variables are not present, correct.

-Dan


> I have a suexec-related question. I'm running (and looking at the source
> code for) Apache 1.3.33.
>
> I am mapping the Host: header to the filesystem path using mod_rewrite and a
> dbm map, for mass virtual hosting. Since "User" can only be set statically
> inside a <VirtualHost> container, it looks like I can't use Apache's suexec,
> so I need something like cgiwrap which runs under the userid of the script
> file itself.
>
> However, this model seems to break down for .shtml pages which contain, say,
>    <!--#exec cmd="foo"-->
>
> Firstly, I need "foo" to run under the userid of the .shtml page (or the
> website owner), not the userid of program "foo".
>
> Secondly, shtml users expect their cmd to be run under a shell; however in
> main/util_script.c, I see that if suexec is active, the shell is not run.
>
>    if (ap_suexec_enabled
> ...
>        if (shellcmd) {
>            execle(SUEXEC_BIN, SUEXEC_BIN, execuser, grpname, argv0,
>                   NULL, env);
>        }
>
> ... but in the non-suexec case:
>
>   else {
>        if (shellcmd) {
>            execle(SHELL_PATH, SHELL_PATH, "-c", argv0, NULL, env);
>        }
>
> So as far as I can tell <!--#exec cmd="echo hello"--> isn't going to work if
> suexec is active, since in suexec, argv[3]="echo hello". I guess the wrapper
> could run a shell, but there's no flag telling it to do so.
>
> I am considering whether I need to write my own setuid wrapper, and pass the
> site username in an environment variable: e.g.
>
> RewriteMap      hostmap         dbm:/conf/hostmap
>
> RewriteCond ${hostmap:${tolower:%{HTTP_HOST}}}    root=(/[^,]+),uid=(\d+)
> RewriteRule ^(.*\.shtml) %1$1 [E=UID:%2]
>
> This seems pretty hairy to me. Or perhaps the wrapper can look at
> SCRIPT_FILENAME or PATH_TRANSLATED and stat() that file?
>
> Anybody have any other suggestions?
>
> A third party module which uses a .db or .cdb file to lookup the "Host:"
> header and set docroot+UID+GID would be acceptable.
>
> Otherwise, I guess what I really want is to be able to set [USER=...] in a
> mod_rewrite rule, but looking at the docs for apache 2.0 and 2.1, I don't
> think this feature has been added.
>
> Regards,
>
> Brian Candler.
>
> ---------------------------------------------------------------------
> The official User-To-User support forum of the Apache HTTP Server Project.
> See <URL:http://httpd.apache.org/userslist.html> for more info.
> To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
>   "   from the digest: users-digest-unsubscribe@httpd.apache.org
> For additional commands, e-mail: users-help@httpd.apache.org
>

--

"Why are you wearing TWO grounding straps?"

-John Evans, Ezzi Computers August 23, 2001


--------Dan Mahoney--------
Techie,  Sysadmin,  WebGeek
Gushi on efnet/undernet IRC
ICQ: 13735144   AIM: LarpGM
Site:  http://www.gushi.org
---------------------------


---------------------------------------------------------------------
The official User-To-User support forum of the Apache HTTP Server Project.
See <URL:http://httpd.apache.org/userslist.html> for more info.
To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
   "   from the digest: users-digest-unsubscribe@httpd.apache.org
For additional commands, e-mail: users-help@httpd.apache.org


Re: [users@httpd] shtml and suexec

Posted by "Dan Mahoney, System Admin" <da...@prime.gushi.org>.
On Thu, 28 Jul 2005, Brian Candler wrote:

Also, if you want the wrapper to run a shell, call a script, like one that 
starts with #!/bin/sh

-Dan


> I have a suexec-related question. I'm running (and looking at the source
> code for) Apache 1.3.33.
>
> I am mapping the Host: header to the filesystem path using mod_rewrite and a
> dbm map, for mass virtual hosting. Since "User" can only be set statically
> inside a <VirtualHost> container, it looks like I can't use Apache's suexec,
> so I need something like cgiwrap which runs under the userid of the script
> file itself.
>
> However, this model seems to break down for .shtml pages which contain, say,
>    <!--#exec cmd="foo"-->
>
> Firstly, I need "foo" to run under the userid of the .shtml page (or the
> website owner), not the userid of program "foo".
>
> Secondly, shtml users expect their cmd to be run under a shell; however in
> main/util_script.c, I see that if suexec is active, the shell is not run.
>
>    if (ap_suexec_enabled
> ...
>        if (shellcmd) {
>            execle(SUEXEC_BIN, SUEXEC_BIN, execuser, grpname, argv0,
>                   NULL, env);
>        }
>
> ... but in the non-suexec case:
>
>   else {
>        if (shellcmd) {
>            execle(SHELL_PATH, SHELL_PATH, "-c", argv0, NULL, env);
>        }
>
> So as far as I can tell <!--#exec cmd="echo hello"--> isn't going to work if
> suexec is active, since in suexec, argv[3]="echo hello". I guess the wrapper
> could run a shell, but there's no flag telling it to do so.
>
> I am considering whether I need to write my own setuid wrapper, and pass the
> site username in an environment variable: e.g.
>
> RewriteMap      hostmap         dbm:/conf/hostmap
>
> RewriteCond ${hostmap:${tolower:%{HTTP_HOST}}}    root=(/[^,]+),uid=(\d+)
> RewriteRule ^(.*\.shtml) %1$1 [E=UID:%2]
>
> This seems pretty hairy to me. Or perhaps the wrapper can look at
> SCRIPT_FILENAME or PATH_TRANSLATED and stat() that file?
>
> Anybody have any other suggestions?
>
> A third party module which uses a .db or .cdb file to lookup the "Host:"
> header and set docroot+UID+GID would be acceptable.
>
> Otherwise, I guess what I really want is to be able to set [USER=...] in a
> mod_rewrite rule, but looking at the docs for apache 2.0 and 2.1, I don't
> think this feature has been added.
>
> Regards,
>
> Brian Candler.
>
> ---------------------------------------------------------------------
> The official User-To-User support forum of the Apache HTTP Server Project.
> See <URL:http://httpd.apache.org/userslist.html> for more info.
> To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
>   "   from the digest: users-digest-unsubscribe@httpd.apache.org
> For additional commands, e-mail: users-help@httpd.apache.org
>

--

"Is Gushi a person or an entity?"
"Yes"

-Bad Karma, August 25th 2001, Ezzi Computers, Quoting himself earler, referring to Gushi

--------Dan Mahoney--------
Techie,  Sysadmin,  WebGeek
Gushi on efnet/undernet IRC
ICQ: 13735144   AIM: LarpGM
Site:  http://www.gushi.org
---------------------------


---------------------------------------------------------------------
The official User-To-User support forum of the Apache HTTP Server Project.
See <URL:http://httpd.apache.org/userslist.html> for more info.
To unsubscribe, e-mail: users-unsubscribe@httpd.apache.org
   "   from the digest: users-digest-unsubscribe@httpd.apache.org
For additional commands, e-mail: users-help@httpd.apache.org