CVE-2023-40547 – shim이 HTTP 헤더를 잘못 신뢰해 발생한 취약점
(github.com/rhboot)- rhboot/shim의 commit
0226b56은 파일 수신 과정에서 HTTP 헤더의 크기 값을 그대로 믿어 발생한 CVE-2023-40547을 수정함 - 조작된 헤더가 실제 수신 데이터보다 작은 크기를 지정하면, shim이 필요한 버퍼보다 작은 공간을 할당할 수 있음
- 기존 코드는 할당에는 헤더 값을 쓰고 복사에는 프로토콜 메타데이터를 사용해 out-of-bounds write로 이어질 수 있었음
- 패치는
httpboot.c의receive_http_response()에서*buf_size < rx_message.BodyLength를 검사하고, 실패 시EFI_BAD_BUFFER_SIZE와Invalid Content-Length로 처리함 - 변경 범위는
httpboot.c1개 파일의 7줄 추가·1줄 삭제이며,Content-Lenght오타도Content-Length로 고쳐짐
취약점 발생 흐름
- CVE-2023-40547는 shim이 HTTP 또는 관련 프로토콜로 파일을 가져올 때 발생하는 문제임
- 수신 데이터를 저장할 버퍼를 할당하는 과정에서 HTTP 헤더의 크기 값이 사용됨
- HTTP 헤더는 조작될 수 있으며, 실제 수신 데이터보다 작은 크기를 지정할 수 있음
- 기존 흐름은 버퍼 할당에는 헤더 값을 쓰고, rx 버퍼에서 데이터를 복사할 때는 프로토콜 메타데이터를 기준으로 삼았음
- 이 차이 때문에 할당된 버퍼보다 큰 데이터를 복사할 수 있고, 그 결과 out-of-bounds write가 발생할 수 있었음
패치 내용
- 패치는
httpboot.c의receive_http_response(EFI_HTTP_PROTOCOL *http, VOID **buffer, UINT64 *buf_size)에 방어 검사를 추가함 *buf_size == 0인 경우 기존 오류 메시지의 오타를 고치고goto error로 이동함Failed to get Content-Lenght→Failed to get Content-Length
- 새 검사는
*buf_size < rx_message.BodyLength조건을 확인함- 조건이 참이면
efi_status = EFI_BAD_BUFFER_SIZE를 설정함 Invalid Content-Length오류를 출력함- 이후
goto error로 이동함
- 조건이 참이면
변경 범위
- 변경된 파일은
httpboot.c1개임 - 변경량은 7줄 추가, 1줄 삭제임
- 핵심은 수신 본문 길이인
rx_message.BodyLength가 할당 크기인*buf_size보다 큰지 확인하는 로직임
관련 기록
- 이 commit은 CVE-2023-40547을 해결하는 변경으로 표시됨
- commit 메시지는 문제가 HTTP 헤더를 잘못 신뢰한 데서 비롯됐다고 명시함
- 취약점 보고자는 Microsoft Security Response Center의 Bill Demirkapi로 기록됨
댓글과 토론
Hacker News 의견들
-
shim은 Secure Boot를 켜려는 Linux 배포판에서 흔히 쓰는 EFI 부트로더임
배포판 입장에서는 사용자가 직접 키를 등록하게 하기보다, 기본 탑재된 Microsoft 서명 키로 Secure Boot를 쉽게 켜고 싶어 함
하지만 Microsoft는 일반적으로 GRUB 같은 GPL 부트로더에 서명해 주지 않기 때문에, Microsoft 키로 서명 가능한 shim이 만들어졌고, shim은 Machine Owner Key, 즉 MOK라는 별도 키로 자신이 부팅할 대상의 서명을 검증함
shim에 부팅할 EFI 바이너리를 지정할 때 HTTP URL을 줄 수 있는데, 이때 HTTP 서버가 악의적이면 범위 밖 쓰기를 유발할 수 있음
다만 보통은 GRUB 같은 로컬 2단계 부트로더를 부팅하는 데 쓰므로, 대부분의 설치에서는 문제가 될 가능성이 낮아 보임
Secure Boot는 처음부터 이미 서명된 바이너리도 DBX 목록으로 폐기할 수 있게 설계됐고, 이 목록을 UEFI에 넣으면 유효한 서명이 있어도 해당 바이너리를 거부함
이 버그가 있는 오래된 shim 바이너리 서명이 목록에 추가되면 각자 장비에서 목록을 갱신할 수 있으며, LVFS 같은 캡슐 업데이트로 배포될 수도 있고 Secure Boot 키와 변수를 직접 관리한다면 https://uefi.org/revocationlistfile에서 목록을 내려받아 등록할 수도 있음- 원 글의 버그 발견자인데, 이 이슈가 HTTP 부팅을 쓸 때만 악용 가능하다는 건 흔한 오해임
그렇다면 Critical 등급이 아니었을 것임
이 버그는 로컬에서 권한 있는 악성코드가 EFI 파티션을 덮어쓸 때, PXE 부팅이 켜진 인접 네트워크에서 중간자 공격을 할 때, HTTP 부팅을 쓸 때 원격 중간자 공격으로 악용될 수 있음
권한 없는 원격 공격자가 중간자 위치에 있고 피해 장비가 HTTP 부팅을 사용하면 직접 접근 없이도 악용 가능함
피해 장비에서 권한과 코드 실행을 얻은 원격 공격자는 피해자가 HTTP 부팅을 쓰지 않더라도 펌웨어가 HTTP를 지원하면 Secure Boot를 우회할 수 있음
예를 들어 부팅 순서 변수를 바꿔 공격자가 제어하는 서버를 지정하거나, EFI 파티션의 부트로더를 정상 shim과 GRUB2 이미지로 덮어쓴 뒤grub.cfg에서 HTTP로 새 shim을 체인로드하게 만들 수 있음
GRUB2의 장치 문법은 HTTP를 포함해 지원되는 장치를 지정할 수 있기 때문임
또한 권한 없는 인접 공격자가 중간자 위치에 있고 피해 장비가 PXE 부팅을 쓰면, PXE의 shim → PXE의 GRUB2 → HTTP의 shim 식으로 연결해 악용할 수 있음 - Microsoft가 GPLv3 라이선스 부트로더인 GRUB에 서명하면, GPLv3 때문에 개발자에게 서명 키 제공을 강제할 권리가 생길 수 있다고 Microsoft 법무팀이 보기 때문임
출처: https://techcommunity.microsoft.com/t5/hardware-dev-center/u... - 부트로더가 왜 네트워크 통신을 하는지 궁금했는데, EFI 바이너리를 HTTP URL로 지정할 수 있다는 설명으로 이해됨
- shim이 Microsoft가 우려하는 문제를 어떻게 피하는지 궁금함
GPLv3의 안티-Tivoization 조항이 Secure Boot 서명 키 제공을 요구할 수 있다면, MOK 서명 키도 요청 시 제공해야 하는 것 아닌가 싶음
그렇다면 누구나 Secure Boot를 통해 간접적으로 부팅될 임의 코드를 서명할 수 있는 키를 얻는 셈인데, Microsoft가 GRUB 같은 GPLv3 프로젝트에 그냥 서명 키를 발급하는 결과와 의미 있게 다른지 모르겠음 - 로컬 2단계 부트로더를 부팅하는 목적이라면 Windows Boot Manager도 같은 역할을 할 수 있지 않나 싶음
오래된 BIOS 장비에서는 WBM이 GRUB을 체인로드하게 해 둔 적이 있는데, UEFI 장비에서는 아직 해 보지 않아서 막히는 지점이 있는지는 모르겠음
- 원 글의 버그 발견자인데, 이 이슈가 HTTP 부팅을 쓸 때만 악용 가능하다는 건 흔한 오해임
-
“왜 신뢰할 수 없거나 침해된 서버에서 부팅하나?”, “서버가 침해됐으면 그냥 악성 바이너리를 보내면 되니 의미 없는 것 아닌가?”라고 궁금해할 수 있는데, 짧게 말하면 shim이 최종적으로 부팅하는 바이너리는 MOK로 서명되어 있어야 함
따라서 침해된 네트워크 안에서 부팅하든, HTTP로 부팅하든, 침해된 서버에서 부팅하든 HTTPS 여부와 무관하게 같은 보안 보장을 유지해야 함
Secure Boot가 다운그레이드를 막지는 않으므로, 침해된 서버가 다운그레이드 공격에 쓰일 수 있다는 사실은 이 취약점과 별개임
다운그레이드 공격 방지는 어차피 더 견고한 방식으로 따로 구현해야 함
다만 shim이 왜 HTTP 부팅을 직접 지원해야 하는지는 모르겠음
MOK로 서명된 두 번째 로컬 EFI 바이너리에서 처리해도 될 텐데, 아마 구현이 비교적 간단한 기능이라고 여겼던 것 같음 -
이 코드가 왜 본문 길이를 두 가지 기준으로 다루는지 모르겠음
RFC 기준으로 HTTP/1.1에서 Content-Length는 HTTP 요청/응답 본문 길이에 대한 권위 있는 정보임
그 길이를 넘어 선로에 있는 데이터는 정의상 다른 메시지의 일부임
반대로 Content-Length가rx_message.BodyLength보다 크다면 아직 전체 메시지를 받지 못했다는 뜻이므로 더 기다리거나 시간 초과 오류를 내야 함
어느 쪽이든rx_message.BodyLength가 Content-Length와 같다는 보장이 없다면 잘못된 값임
더 관대하게 처리하고 싶다면 Content-Length 헤더를 볼 이유가 없고, 그냥rx_message.BodyLength를 버퍼 크기로 삼아 선로상의 모든 데이터를 수신 메시지로 해석하면 됨
현재 코드는 불필요하게 복잡하고, 이런 식으로 버그가 들어옴- 해당 커밋만 따로 보면 오해하기 쉬움
주변 코드 https://github.com/rhboot/shim/blob/0226b56513b2b8bd5fd281bc...를 보면 루프 안에서 데이터 조각을 받으며 매번 새 데이터가 Content-Length로 정해진 버퍼 용량을 넘지 않는지 확인함
하지만 이전에는 루프 밖의 첫 번째 읽기에 대해 그 검사를 하지 않았고, 그게 버그였음
다만 다운로드된 크기가*buf_size, 즉 Content-Length와 같은지 최종 확인하는 코드는 보이지 않음
이 조건이 깨지면 연결이 너무 일찍 닫혔다는 신호일 수 있음
- 해당 커밋만 따로 보면 오해하기 쉬움
-
이건 분명 버그이고 고쳐져서 다행이지만, 누가 신뢰할 수 없는 호스트에서 자기 장비를 부팅하나 싶음
공격자가 악성 헤더를 보낼 만큼 HTTP 서비스를 장악했다면 이 오버플로를 피하는 건 가장 작은 문제이고, 인증서도 침해됐으며 규격에 맞는 페이로드에 악성코드를 넣어 보낼 수도 있음
버그인 건 맞지만 Critical인지는 확신이 안 듦- Secure Boot를 밀어붙이는 사람들도 비슷한 부류임
이들은 잠재적으로 침해될 수 있는 모든 것이 Secure Boot로 서명되지 않아야만 하는 보안 전략을 진지하게 믿음
취약점이 있는 서명된 무언가가 하나라도 있으면, 그걸 이용해 모두의 Secure Boot+TPM 암호화 디스크를 복호화하는 데 쓸 수 있음
이런 접근이 왜 유효한 보안 모델로 여겨졌는지 이해하기 어렵고, 이런 취약점은 이미 한가득 있음
게다가 방 안의 코끼리인 Windows는 완전히 무시함
이런 사고방식의 예: https://lkml.org/lkml/2018/4/3/767
Linus의 우려에도 불구하고 많은 배포판에서는 Secure Boot로 부팅하면 실제로 무결성 모드가 켜짐
아마 Microsoft 정책과 배포판들이 Microsoft UEFI 서명을 받기 위해 해당 스레드에서 설명된 절차를 따르도록 강제되는 상황 때문일 가능성이 큼
그 결과 Secure Boot를 켜면 보통 배포판 기능이 제한되고, 예를 들어 최대 절전 모드를 쓰지 못하게 됨 - 좋은 방어는 심층 방어처럼 여러 방어층을 쌓는 방식뿐이고, 이 버그는 그중 한 층에 구멍을 냄
- 일부 잠긴 장치에 침입하는 데 쓰일 수도 있음
- 다른 스레드의 훌륭한 설명을 보면 https://news.ycombinator.com/item?id=39135275 공격 벡터가 HTTP에만 국한되지 않기 때문에 Critical로 볼 수 있음
- Secure Boot를 밀어붙이는 사람들도 비슷한 부류임
-
중요한 일이 벌어질 때는 HTTP의 S를 써야 함
장치 부팅도 여기에 포함되고, HTTPS 헤더는 항상 암호화되어 있었음
그래도 잘 찾아낸 버그임- 여기서는 HTTPS가 무관함
잘못된 헤더는 어느 쪽으로도 보낼 수 있음 - HTTPS가 이 용도에서 가능한지 잘 모르겠음
암호화를 위해 정확한 시간과 날짜가 필요하기 때문임
RTC가 유효할 수도 있지만, 시간대를 잘 처리하는지도 모르겠고 어쨌든 시간이 틀어졌을 수도 있음 - S가 있느냐 없느냐와는 관계없는 문제임
문제를 제대로 이해하지 못한 것 같음
- 여기서는 HTTPS가 무관함
-
Content-length는 실제 본문 길이가 아니라 Content-encoding 이후의 길이임- “HTTP/1.1은 대부분을 무시하면 참 즐겁게 단순한 프로토콜임”
-
이 shim 빌드들에 httpboot가 포함되어 있나?
내가 알기로 shim은 디스크의 다른 EFI 바이너리를 실행하기 위한 것일 뿐이고, shim의 네트워크 부팅 기능이 실제로 쓰이는 건 본 적이 없는 것 같음 -
잘 모르는 걸 수도 있지만, 대부분의 HTTP 클라이언트는 지정된 Content-Length까지만 읽고, 읽은 바이트가 Content-Length보다 적으면 오류로 보는 줄 알았음
- HTTP 클라이언트는 UEFI가 EFI 드라이버로 제공함
내가 보기엔 UEFI 사양이 Content-Length 헤더와 응답 본문 길이가 맞지 않을 때의 동작을 구체적으로 정하지 않는 듯함
그래서 일부 구현은 그냥connection:close요청을 만들고 Content-Length를 확인하지 않을 가능성도 충분함
이 취약점은 MSRC가 보고했고 CVE 설명에는 실제 악용에 대한 내용이 없음
나중에 공개될 수도 있고, 이론적인 문제일 수도 있음
- HTTP 클라이언트는 UEFI가 EFI 드라이버로 제공함
-
실제 본문 길이보다 적게 읽는 게 왜 위험한지 설명해 줄 수 있나?
오히려 반대가 위험할 거라고 예상했음- HTTP나 관련 프로토콜로 파일을 가져올 때 shim은 수신 데이터를 저장할 버퍼를 할당하려고 함
그런데 크기를 조작 가능한 HTTP 헤더에서 가져오게 되고, 공격자는 수신 데이터보다 작은 크기를 지정할 수 있음
이 경우 코드가 할당에는 헤더 값을 쓰고, 수신 버퍼에서 복사할 때는 프로토콜 메타데이터의 크기를 사용해서 범위 밖 쓰기가 발생함 - 설명상으로는 Content-Length를 기준으로 버퍼를 할당하지만, 실제로 받은 버퍼의 크기만큼 복사해서 할당 범위를 벗어나 쓰게 됨
- HTTP나 관련 프로토콜로 파일을 가져올 때 shim은 수신 데이터를 저장할 버퍼를 할당하려고 함