3

I'm messing around with the security of a setuid binary (with the intention of disclosing anything I find to the author, obviously). I'm pretty sure it has an arbitrary code execution vulnerability because it invokes a shell script and it doesn't sanitize the environment - I thought of bash's export -f but I can't actually make a proof of concept work.

The basic problem is that for some reason, BASH_FUNC_foobar%% (where foobar is the function being exported) is mysteriously vanishing from the environment in some subprocesses. It works with non-shells:

% env 'BASH_FUNC_foobar%%=() { echo pwd lol; }' env | grep BASH
BASH_FUNC_foobar%%=() { echo pwd lol; }

But if I replace env | grep BASH with the actual program name, modified to dump the environment (basically just system("env") in the C source), this variable is removed. The same happens when I specify sh as the command to invoke, although weirdly enough, if I specify bash then the function is picked up just fine.

Note that on my test system /bin/sh is provided by dash.

What the heck is going on? Why is this variable disappearing?

strugee
  • 14,723
  • 17
  • 73
  • 119
  • 1
    How do you dump the environment in the program? What shell is your `sh`? – ilkkachu Feb 05 '19 at 21:15
  • Possibly related: [Bash - Functions in Shell Variables](//unix.stackexchange.com/q/233091) – Kusalananda Feb 05 '19 at 21:20
  • 1
    Possibly related: [When was the shellshock (CVE-2014-6271/7169) bug introduced, and what is the patch that fully fixes it?](//unix.stackexchange.com/a/157495) – Kusalananda Feb 05 '19 at 21:20
  • dash filters the inbound environment to only valid variable names, if that's your `sh`. – Michael Homer Feb 05 '19 at 21:25
  • @ilkkachu edited the question to include those answers. – strugee Feb 06 '19 at 19:02
  • 2
    @strugee, yep, the use of `system()` clears that one up. Something like `extern char **environ; for (char **p = environ; *p; p++) printf("%s\n", *p);` should show the ones with non-valid names too. – ilkkachu Feb 06 '19 at 19:07
  • @strugee, also note that is Bash detects it's running setuid, it doesn't load exported functions, so the program might not be immediately vulnerable that way even if `sh` is Bash (though it should still clear the environment). See the final point here: https://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html#Bash-Startup-Files – ilkkachu Feb 06 '19 at 19:11

1 Answers1

4

On many linux systems, /bin/sh is actually /bin/dash.

As already mentioned in a comment by @MichaelHomer /bin/dash will sanitize the environment and remove from it any strings which are not of the form /^[a-zA-Z_][a-zA-Z_0-9]*=.*/, and that includes the BASH_FUNC_foo%%=..., because of the %%.

This is quite specific to dash and to OpenBSD's ksh (and mksh which is based on it) -- other shells won't bother to do that.

Example, on debian where /bin/sh is dash:

$ env - '@#%=' 'foo%%=bar' /bin/sh -c /usr/bin/printenv
PWD=/your/cwd
$ env - '@#%=' 'foo%%=bar' /usr/bin/printenv
@#%=
foo%%=bar
$ env - '@#%=' 'foo%%=bar' /bin/bash -c /usr/bin/printenv
[...]
@#%=
foo%%=bar

For a source reference you can look at src/var.c in the dash source:

    initvar();
    for (envp = environ ; *envp ; envp++) {
            p = endofname(*envp);
            if (p != *envp && *p == '=') {
                    setvareq(*envp, VEXPORT|VTEXTFIXED);
            }
    }

When dash exec's another binary, the env argument passed to execve is built from the variable list dynamically (with listvars(VEXPORT, VUNSET, 0) as called via the environment() macro).

  • this explains it. libc `system()` will also invoke `/bin/sh` which explains why the C program was mysteriously deleting these variables, too. – strugee Feb 06 '19 at 19:04