1. 대체 경로, 대체 채널

- 인증이나 정상적인 보안 메커니즘을 우회하는 공격 경로를 의미 (CWE-288) [1]

 

- 대체 경로(Alternate Path)
시스템이 정상적으로 인증을 요구하는 주요 경로(로그인 페이지 등)를 우회할 수 있는 비표준적인(≒비공식적인) 경로
> 디버깅용 또는 개발 테스트용 백도어가 남아있는 경우
> 사용자 입력 값(URL 매개변수)을 조작해 비인가된 데이터를 조회
> 잘못된 권한 검증으로 특정 파일 또는 디렉토리에 접근

> 대응 : 디버깅 및 테스트 코드 제거, 입력 값 검증, 숨겨진 디렉터리 접근 방지 등

 

- 대체 채널(Alternate Channel)
정상적인 인증 프로세스와 보안 채널(HTTPS 등)을 우회하여 비정상적인 통신 채널을 통해 시스템에 접근하거나 데이터를 전송하는 것
> HTTP 요청을 통해 정보 전송 및 평문 가로채기
> 레거시 프로토콜을 통해 암호화되지 않은 정보 가로채기
> 모바일 앱, IoT 기기에서 SSL/TLS 인증서 검증이 불완전해 중간자 공격으로 데이터 탈취

> 대응 : HTTPS 강제 적용, 레거시 프로토콜 비활성화, SSL/TLS 검증 강화, 네트워크 모니터링 및 로그 관리 등

2. CVE-2024-55591

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

- Fortinet 제품의 대체 경로 또는 대체 채널 문제로 인해 발생하는 인증 우회 취약점 (CVSS: 9.8)

> 공격자는 Node.js 웹소켓 모듈에 대한 조작된 요청을 통해 슈퍼 관리자 권한을 얻을 수 있음

※ 취약점 관련 상세 내용 확인 불가

영향받는 제품
- FortiOS 7.0 : 7.0.0 이상 ~ 7.0.16 이하
- FortiProxy : 7.2.0 이상 ~ 7.2.12 이하 / 7.0.0 이상 ~ 7.0.19 이하

 

- 관련 PoC [3]

> 총 5개의 조건을 충족할 경우 취약 (status_code_1_check, status_code_2_check, body_checks, header_marker_check, connection_upgrade_check)

구분 설명
status_code_1_check - 첫 번째 요청 및 응답으로 해당 페이지가 로그인 관련 페이지인지 확인

first_url = f"{base_url}/login?redir=/ng"
first_response = requests.get(first_url, verify=False, timeout=10)
<중략>
status_code_1_check = first_response.status_code == 200
status_code_2_check - 두 번째 요청 및 응답으로 WebSocket 업그레이드 가능 여부 확인

second_url = f"{base_url}/watchTowr-{random_suffix}"
second_headers = {
'Sec-WebSocket-Version': '13',
'Sec-WebSocket-Key': 'thFz/fKwzu5wDEy0XO3fcw==',
'Connection': 'keep-alive, Upgrade',
'Upgrade': 'websocket'
}
second_response = requests.get(second_url, headers=second_headers, verify=False, timeout=10)
<중략>
status_code_2_check = second_response.status_code == 101

※ 응답코드 101 : 클라이언트가 보낸 업그레이드 요청 헤더에 대한 응답으로, 서버가 클라이언트의 Upgrade 요청을 받아들여 프로토콜을 변경할 것임을 알림, 주로 WebSocket 프로토콜 전환 시 사용 [4]
body_checks - 첫 번째 응답에서 세 가지 값 (html_main_app_check,  f_icon_warning_check, f_icon_closing_check)의 만족 여부 확인

html_main_app_check = '<html class="main-app">' in first_response.text
f_icon_warning_check = '<f-icon class="fa-warning' in first_response.text
f_icon_closing_check = '</f-icon>' in first_response.text
<중략>
body_checks = html_main_app_check and f_icon_warning_check and f_icon_closing_check
header_marker_check - 첫 번째 응답에서 특정 헤더 (APSCOOKIE_)의 존재 여부 확인

header_marker_check = any('APSCOOKIE_' in str(header) for header in first_response.headers.values())
connection_upgrade_check - 두 번째 응답에서 Connection 헤더의 값 확인

connection_upgrade_check = 'Upgrade' in second_response.headers.get('Connection', '')
import requests
import random
from uuid import uuid4
from datetime import datetime, timedelta
import argparse

banner = """\
             __         ___  ___________                   
     __  _  ______ _/  |__ ____ |  |_\\__    ____\\____  _  ________ 
     \\ \\/ \\/ \\__  \\    ___/ ___\\|  |  \\\\|    | /  _ \\ \\/ \\/ \\_  __ \\
      \\     / / __ \\|  | \\  \\\\___|   Y  |    |(  <_> \\     / |  | \\\n       \\/\\_/ (____  |__|  \\\\\\\___  |___|__|__  | \\\\__  / \\\/\\_/  |__|   
                  \\\          \\\     \\\                              

        CVE-2024-55591.py
        (*) Fortinet FortiOS Authentication Bypass (CVE-2024-55591) vulnerable detection by watchTowr
        
          - Sonny , watchTowr (sonny@watchTowr.com)
          - Aliz Hammond, watchTowr (aliz@watchTowr.com)

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

def generate_random_suffix(length=6):
    """Generate a random lowercase suffix."""
    return ''.join(random.choice('abcdefghijklmnopqrstuvwxyz') for _ in range(length))

def perform_web_interaction(target, port):
    """
    Perform a two-step web interaction with specific parameters.
    
    Args:
        target (str): Target IP address
        port (int): Target port
    
    Returns:
        tuple: Results of the two requests
    """
    # Construct base URL
    base_url = f"https://{target}:{port}"
    
    # Generate random suffix
    random_suffix = generate_random_suffix()
    
    # Disable SSL verification warnings
    requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
    
    # First request - login-like endpoint
    first_url = f"{base_url}/login?redir=/ng"
    first_response = requests.get(first_url, verify=False, timeout=10)
    
    # Second request - endpoint with random suffix
    second_url = f"{base_url}/watchTowr-{random_suffix}"
    second_headers = {
        'Sec-WebSocket-Version': '13',
        'Sec-WebSocket-Key': 'thFz/fKwzu5wDEy0XO3fcw==',
        'Connection': 'keep-alive, Upgrade',
        'Upgrade': 'websocket'
    }
    second_response = requests.get(second_url, headers=second_headers, verify=False, timeout=10)
    
    return first_response, second_response

def validate_interaction_conditions(first_response, second_response):
    """
    Validate specific conditions for the web interaction.
    
    Args:
        first_response (requests.Response): First HTTP response
        second_response (requests.Response): Second HTTP response
    
    Returns:
        bool: Whether all conditions are met
    """
    try:
        # Check status codes
        status_code_1_check = first_response.status_code == 200
        status_code_2_check = second_response.status_code == 101
        
        # Check body contents for first response
        html_main_app_check = '<html class="main-app">' in first_response.text
        f_icon_warning_check = '<f-icon class="fa-warning' in first_response.text
        f_icon_closing_check = '</f-icon>' in first_response.text
        
        body_checks = html_main_app_check and f_icon_warning_check and f_icon_closing_check
        
        # Check for specific header marker
        header_marker_check = any('APSCOOKIE_' in str(header) for header in first_response.headers.values())
        
        # Check connection upgrade for second response
        connection_upgrade_check = 'Upgrade' in second_response.headers.get('Connection', '')
        
        # Print detailed information about first response matchers
        if not html_main_app_check:
            print("[!] Target is not a FortiOS Management Interface")
            exit()
        
        if not f_icon_warning_check:
            print("[!] '<f-icon class=\"fa-warning\"' not found in response")

        # Combine all checks
        return all([
            status_code_1_check,
            status_code_2_check,
            body_checks,
            header_marker_check,
            connection_upgrade_check
        ])
    except Exception as e:
        print(f"[!] Error during validation: {e}")
        return False

def main():
    """
    Main function to run the web interaction checks.
    """
    print(banner)

    parser = argparse.ArgumentParser(description='CVE-2024-55591 Detection Tool')
    parser.add_argument('--target', '-t', type=str, help='IP address of the target', required=True)
    parser.add_argument('--port', '-p', type=int, help='Port of the target', required=False, default=443)
    args = parser.parse_args()

    try:
        print(f"[*] Targeting: https://{args.target}:{args.port}")
        first_response, second_response = perform_web_interaction(args.target, args.port)
        
        result = validate_interaction_conditions(first_response, second_response)
        
        if result:
            print("[!] VULNERABLE: All conditions were met")
        else:
            print("[*] NOT VULNERABLE: Conditions were not satisfied")
        
    except requests.RequestException as e:
        print(f"[!] Request error: {e}")
    except Exception as e:
        print(f"[!] Unexpected error: {e}")

if __name__ == "__main__":
    main()

3. 대응방안

- 벤더사 제공 보안 업데이트 적용 [5]

제품명 영향받는 버전 해결 버전
FortiOS 7.0 7.0.0 이상 ~ 7.0.16 이하 7.0.17 이상
FortiProxy 7.2.0 이상 ~ 7.2.12 이하 7.2.13 이상
7.0.0 이상 ~ 7.0.19 이하 7.0.20 이상

 

> Fortinet 권고사항 [6]

구분 설명
로그 점검 - 랜덤 scrip 및 dstip가 포함된 로그인 활동 관련 로그:
type="event" subtype="system" level="information" vd="root" logdesc="Admin login successful" sn="1733486785" user="admin" ui="jsconsole" method="jsconsole" srcip=1.1.1.1 dstip=1.1.1.1 action="login" status="success" reason="none" profile="super_admin" msg="Administrator admin logged in successfully from jsconsole"

-  무작위로 생성된 것처럼 보이는 사용자 이름과 소스 IP가 포함된 관리자 생성 로그:
type="event" subtype="system" level="information" vd="root" logdesc="Object attribute configured" user="admin" ui="jsconsole(127.0.0.1)" action="Add" cfgtid=1411317760 cfgpath="system.admin" cfgobj="vOcep" cfgattr="password[*]accprofile[super_admin]vdom[root]" msg="Add system.admin vOcep"

-  로그에서 공격자들이 사용한 것으로 나타난 IP 주소:
1.1.1[.]1
127.0.0[.]1
2.2.2[.]2
8.8.8[.]8
8.8.4[.]4
위협행위자 수행 작업 점검 - 임의의 사용자 이름으로 장치에 관리자 계정 생성
- 임의의 사용자 이름으로 장치에 로컬 사용자 계정 생성
- 사용자 그룹 생성 또는 기존 sslvpn 사용자 그룹에 위의 로컬 사용자 추가
- 기타 설정(방화벽 정책, 방화벽 주소 등) 추가/변경
- 위에 추가된 로컬 사용자로 sslvpn에 로그인하여 내부 네트워크로 가는 터널 생성
공격 IP 점검 및 차단 -  45.55.158[.]47 [가장 많이 사용되는 IP 주소]
-  87.249.138[.]47
-  155.133.4[.]175
-  37.19.196[.]65
-  149.22.94[.]37
권고 - HTTP/HTTPS 관리 인터페이스 비활성화
- 로컬-인 정책을 통해 관리 인터페이스에 접근할 수 있는 IP 주소를 제한

4. 참고

[1] https://cwe.mitre.org/data/definitions/288.html
[2] https://nvd.nist.gov/vuln/detail/CVE-2024-55591
[3] https://github.com/watchtowrlabs/fortios-auth-bypass-check-CVE-2024-55591/tree/main?tab=readme-ov-file
[4] https://docs.whatap.io/url/url-http-status
[5] https://www.boho.or.kr/kr/bbs/view.do?searchCnd=&bbsId=B0000133&searchWrd=&menuNo=205020&pageIndex=1&categoryCode=&nttId=71632
[6] https://www.fortiguard.com/psirt/FG-IR-24-535
[7] https://arcticwolf.com/resources/blog/console-chaos-targets-fortinet-fortigate-firewalls/
[8] https://www.dailysecu.com/news/articleView.html?idxno=163036

+ Recent posts