Sunday, August 8, 2021

; Echo “Shell Injection”

To understand what have just happened, we need to learn a bit about how spawning a process works in general. This section is somewhat UNIX-specific — things are implemented a bit differently on Windows. Nonetheless, the big picture conclusions hold there as well.

The main API to run a program with command line arguments is the exec family of functions. For example, here’s execve:

1
2
int execve(const char *pathname, char *const argv[],
           char *const envp[]);

It takes the name of the program (pathname), a list of command line arguments (argv), and a list of environment variable for the new process (envp), and uses those to run the specified binary. How exactly this happens is a fascinating story with many forks in the plot, but it is beyond the scope of the article.

What is curious though, is that while the underlying system API wants an array of arguments, the child_process.exec function from node takes only a single string: exec("curl http://example.com").

Let’s find out! To do that, we’ll use the strace tool. This tool inspects (traces) all the system calls invoked by the program. We’ll ask strace to look for execve in particular, to understand how node’s exec maps to the underlying system’s API. We’ll need the --follow argument to trace all processes, and not just the top-level one. To reduce the amount of output and only print execve, we’ll use the --trace flag:

1
2
3
4
5
6
$ strace --follow --trace execve node main.js < urls.txt
execve("/bin/node", ["node", "curl-all.js"], 0x7fff97776be0)
...
execve("/bin/sh", ["/bin/sh", "-c", "curl https://example.com"], 0x3fcacc0)
...
execve("/bin/curl", ["curl", "https://example.com"], 0xec4008)

The first execve we see here is our original invocation of the node binary itself. The last one is what we want to do — spawn curl with a single argument, an url. And the middle one is what node’s exec actually does.

Let’s take a closer look:

1
/bin/sh -c "curl https://example.com"

Here, node invokes the sh binary (system’s shell) with two arguments: -c and the string we originally passed to child_process.exec. -c stands for command, and instructs the shell to interpret the value as a shell command, parse, it and then run it.

In other words, rather then running the command directly, node asks the shell to do the heavy lifting. But the shell is an interpreter of the shell language, and, by carefully crafting the input to exec, we can ask it to run arbitrary code. In particular, that’s what we used as a payload in the bad example above:

malice_in_the_wonderland.txt

1
; echo 'PWNED, reading your secrets from /etc/passwd' && cat /etc/passwd

After the string interpolation, the resulting command was

1
/bin/sh -c "curl; echo '...' && cat /etc/passwd"

That is, first run curl, then echo, then read the /etc/passwd.



from Hacker News https://ift.tt/3BZ1QbO

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.