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?


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
$ env - '@#%=' 'foo%%=bar' /usr/bin/printenv
$ env - '@#%=' 'foo%%=bar' /bin/bash -c /usr/bin/printenv

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

    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 6 '19 at 19:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.