No need for sendmail: use LD_PRELOAD to break through disable_functions

Category: Tag:

When you get the webshell with all your hard work, but cannot execute system commands, you suspect that the server-side disable_functions disables the command execution function. You want to hijack system functions through the environment variable LD_PRELOAD, but you find that the target does not have sendmail installed at all, and the webshell that cannot execute commands is meaningless. What should I do?

I visited an e-commerce website half a month ago, and briefly browsed the functions, only registering, logging in, placing an order, paying and so on. In the login interface, I found a RCE (remote code execution, non-remote command execution) vulnerability,After I write the chopper trojan, connect:

Traditionally, after getshell, I will first understand the system configuration, execute cat /proc/meminfo in the virtual terminal, but the execution error:

It is suspected that WAF has hijacked the command to be executed, and I tried several commonly used methods to bypass command execution restrictions with empty strings, path expansion, and custom variables, but all failed:

The webshell without command execution function is meaningless, it must be a breakthrough!

Generally speaking, there are roughly three reasons why webshell cannot execute commands: one is that the system(), exec() and other related functions executed by such commands are disabled with the disable_functions indicator in php.ini; the other is that the web process is running in In a restricted shell environment such as rbash; the third is WAF hijacking. If it is one, no command can be executed, and if it is two or three, a few commands can be executed. Judging from the current phenomenon, it may be caused by disable_functions. For verification, I used the previous RCE vulnerability to execute phpinfo():

There are four ways to bypass disable_functions: the first is to attack the back-end components, looking for back-end components commonly used in web applications with command injection, such as the magic map vulnerability of ImageMagick and the shell-breaking vulnerability of bash; the second, Look for functions that are not disabled. Commonly used commands to execute commands include system(), exec(), shell_exec(), passthru(), popen(), proc_open(), pcntl_exec(), try one by one; In mod_cgi mode, try to modify .htaccess, adjust request access routing, and bypass any restrictions in php.ini; fourth, use the environment variable LD_PRELOAD to hijack system functions and let external programs load malicious *.so to execute system commands effect.

When I tried the first one, I used phpinfo() to check that the ImageMagick version was v6.9.4-10:

Use searchsploit (the local version of exploit-db.com) to search for v6.9.3-9 or v7.0.1-0 with command injection:

Obviously, the current ImageMagick version cannot be used; when you try the second method, common, uncommon, and rare (such as dl()), all functions that can start processes are disabled; when you try the third method, you find that Mod_cgi mode is not enabled. All hope is pinned in LD_PRELOAD.

Imagine such an idea: use vulnerability to control the web to start a new process a.bin (even if the process name cannot be specified by me), use a.bin to internally call the system function b(), b() is located in the system shared object c.so , So the system loads the shared object c.so for this process. I think of a way to load controllable c_evil.so before c.so. c_evil.so contains malicious functions with the same name as b(), because c_evil.so is preferred High-level. Therefore, a.bin will call b() in c_evil.so instead of b() in c.so of the system. At the same time, c_evil.so is controllable to achieve the purpose of executing malicious code. Based on this idea, the goal of restricting the execution of operating system commands by disable_functions will be broken. I roughly break it down into a few steps to deduct it locally: view the process calling system function details, hijack the system function injection code in the operating system environment, find the PHP function that starts a new process internally, and hijack the system function injection code in the PHP environment.

View the details of the process calling system functions. The process of creating a new process in Linux is more complicated. I care about which shared objects the process loads, which APIs may be called, and which APIs are actually called. For example, running /usr/bin/id, you can view the shared objects loaded by the system through ldd:

Since the executable file /usr/bin/id contains a symbol table, run nm -D /usr/bin/id 2>&1 or readelf -Ws /usr/bin/id to view the list of system APIs that the program may call :

Because the program will react differently according to the command line options and the operating environment when the program is running, the API called at the real runtime may be only a subset of the readefl view. You can run strace -f /usr/bin/id 2>&1 to track the actual API call The situation, for example, the input parameters and return value of the actual call to open() are clear at a glance:

Hijack system functions to inject code in the operating system environment. The Linux environment variable LD_PRELOAD is a more elegant implementation similar to the win32 API hook, which is suitable for hot patching, reading process space data, prohibiting programs from calling specified APIs, debugging programs, etc., even without changing the original executable The backdoor is implanted under the premise of the file (/bin/ps commonly used by the administrator). Since the hijacked system functions have to be re-implemented by us, the function prototypes must be consistent. To reduce complexity, I will choose to hijack those commonly used system functions without parameters. Getuid() is suitable. Take this as an example to complete the hijacking process. The steps are roughly as follows: First, use man 2 getuid to view the function prototype:

Then, write the getuid() function of the same prototype and save it to getuid_shadow.c. The source code is:

Execute gcc -shared -fPIC getuid_shadow.c -o getuid_shadow.so to compile it into a shared object:

Finally, use the environment variable LD_PRELOAD to hijack the system function getuid() to gain control. Execute LD_PRELOAD=/root/getuid_shadow.so /usr/bin/id, the injected code is successfully executed:

Note that LD_PRELOAD is a process exclusive environment variable, similar to a command adapter, it must be a blank character between it and the command to be executed, not a command separator (;, &&, ||).

Look for the PHP function that starts a new process internally. Although LD_PRELOAD provides me with the ability to hijack system functions, the premise is that I have to control php to start external programs (as long as there is process startup behavior, it doesn’t matter who it is). The common system() method of starting the program is obviously not working, otherwise there will be no breakthrough in disable_functions. Apart from calling a bunch of php functions such as system(), exec(), shell_exec(), etc. in a PHP script, what else is possible to start an external program? The php interpreter itself! For example, the php function goForward() implements the “forward” function. The php function goForward() is implemented by move.c, one of the C language modules that make up the php interpreter. The C module move.c internally calls the external program go. bin implementation, then, the function goForward() is called in my php script, which is bound to start the external program go.bin. Now, I need to find a real PHP function like goForward(). In my impression, the functions I want may exist in the three scenarios of processing pictures, requesting web pages, and sending emails, and I have to verify them one by one. To process images, usually call the ImageMagick library packaged by PHP, create a new image.php, and call Imagick():

Run strace -f php image.php 2>&1 | grep -A2 -B2 execve to check whether Imagick() starts a new process:

The first execve is to start the PHP interpreter. The second execve must be found. If it is not, it means that a new process has not been started; request a web page, create a new http.php, and call curl_init():

Run strace -f php http.php 2>&1 | grep -A2 -B2 execve to check whether curl_init() starts a new process:

Still not what I want; to send an email, create a new mail.php and call mail():

Run strace -f php mail.php 2>&1 | grep -A2 -B2 execve to check whether mail() starts a new process:

bingo! Two new processes, /usr/sbin/sendmail and /usr/sbin/postdrop, are started inside mail(). It is the function I have been looking for (using the same test method, I also found an imap_mail()).

Hijacking system functions to inject code in the PHP environment. Add the code for setting LD_PRELOAD in mail.php:

Then put mail.php and the shared object getuid_shadow.so containing the mail() function into the web directory /var/www/:

After executing mail.php, find the file /tmp/evil created by mail() in getuid_shadow.so, and execute the command successfully in the PHP environment without using any PHP command execution function:

With the previous analysis, let’s see how I can bypass disable_functions and execute system commands on the target site.

First, based on the previous mail.php, I wrote a small Trojan bypass_disablefunc.php:

bypass_disablefunc.php provides three GET parameters. The first is the cmd parameter, the system command to be executed (such as pwd); the second is the outpath parameter, the file path that saves the output result of the command execution (such as /tmp/xx) for easy display on the page. In addition, you should pay attention to this parameter Does the web have read and write permissions, whether the web can be accessed across directories, and files will be overwritten and deleted; the third is the sopath parameter, which specifies the absolute path of the shared object that hijacks the system function (such as /var/www/bypass_disablefunc_x64.so) In addition, regarding this parameter, you should pay attention to whether it can be accessed across directories on the web. In addition, the bypass_disablefunc.php splicing command and output path become a complete command line, so you don’t need to redirect in the cmd parameter:

$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";

At the same time, through the environment variable EVIL_CMDLINE to bypass_disablefunc_x64.so to pass the specific command line information:

putenv("EVIL_CMDLINE=" . $evil_cmdline);

Then, based on getuid_shadow.c, write the code of the hijack function bypass_disablefunc.c. Recall that the reason why I hijacked getuid() earlier was because the sendmail program called this function. In real environments, there are two problems: first, in some environments, the web prohibits senmail from being enabled, and even sendmail is not installed on the system. , It can’t talk about hijacking getuid(), the usual www-data permission is impossible to change the php.ini configuration and install the sendmail software; second, even if the target can enable sendmail, the hostname (hostname output) is not Adding it to the hosts will cause it to take half a minute to wait for the domain name resolution timeout to return every time you run sendmail, and www-data cannot add the host name to the hosts (for example, 127.0.0.1 lamp, lamp., lamp.com). For these two reasons, I had to give up hijacking the function getuid() and had to find a more convenient method. Speaking of LD_PRELOAD, the system preloads shared objects through it. If you can find a way to execute the code when loading, without considering hijacking a certain system function, then I can completely independent of sendmail. This scenario is very similar to the C++ constructor! I learned that GCC has a C language extension modifier __attribute__((constructor)), which can make the function modified by it execute before main(). If it appears in the shared object, then once the shared object is loaded by the system , The function decorated by __attribute__((constructor)) will be executed immediately. I emphasize that this detail is very important. Many friends use LD_PRELOAD to break through disable_functions and cannot achieve 100% success. For this reason, we should not limit ourselves to hijacking a function, but consider hijacking shared objects. The source code of bypass_disablefunc.c is as follows:

Receive the command line to be executed from bypass_disablefunc.php from the environment variable EVIL_CMDLINE.

Then, use the command gcc -shared -fPIC bypass_disablefunc.c -o bypass_disablefunc_x64.so to compile bypass_disablefunc.c into a shared object bypass_disablefunc_x64.so:

You need to compile into different versions according to the target architecture, and compile in an x64 environment. If you don’t have a compiler option, the default is x64. If you want to compile to an x86 architecture, you need to add the -m32 option.

Finally, upload bypass_disablefunc.php and bypass_disablefunc_x64.so to the target:

After specifying the command output path and shared object path, execute the previously failed command cat /proc/meminfo on bypass_disablefunc.php again:

Aha! Cool, right.

Well, this is how you use LD_PRELOAD to break through disable_functions. The only condition is that PHP supports putenv() and mail(), and you don’t even need to install sendmail. bypass_disablefunc.php, bypass_disablefunc.c, bypass_disablefunc_x64.so:

download here

 

Reviews

There are no reviews yet.

Be the first to review “No need for sendmail: use LD_PRELOAD to break through disable_functions”

Your email address will not be published. Required fields are marked *