Reddit

CVE-2026-91766: PHP had the redirect credential leak curl fixed in 2018

CVE-2026-91766: PHP 리다이렉트에서 인증 정보 유출, curl은 2018년에 수정한 문제

PHP의 file_get_contents()와 fopen()은 리다이렉트를 따라갈 때 Authorization, Cookie 같은 헤더를 다른 출처에도 보냈습니다. PHP 8.2.34, 8.3.35, 8.4.26, 8.5.11에서 출처의 스킴·호스트·포트를 비교해 인증 헤더를 제거하도록 수정했습니다.

AI 요약

PHP의 file_get_contents()나 fopen()에 Authorization 또는 Cookie 헤더를 지정하고 리다이렉트를 따라가게 두면, 리다이렉트 대상이 다른 호스트여도 같은 헤더를 보냈습니다. HTTPS에서 HTTP로 내려가거나 포트만 바뀌는 경우도 포함됩니다. HTTP stream wrapper가 리다이렉트마다 같은 stream context를 재사용하면서 Content-Length와 Content-Type만 제거하고 인증 헤더는 그대로 넘긴 탓입니다.

취약점이 생긴 경로

URL을 여는 PHP 코드에서 stream_context_create()로 헤더를 지정하면, 기본 설정인 follow_location이 리다이렉트를 따라갑니다. 기존 코드는 리다이렉트 응답을 받은 뒤 현재 URL 정보를 해제하고 새 URL을 읽었습니다. 다음 요청은 같은 context를 받아 헤더를 다시 구성했으며, 현재 URL과 새 URL의 출처를 비교하지 않았습니다.

이 동작은 2003년 헤더·메서드·본문을 stream context 옵션으로 추가할 때부터 이어졌습니다. 2005년에는 POST가 리다이렉트 뒤 GET으로 바뀔 때 브라우저와 curl의 동작을 따르도록 Content-Length와 Content-Type을 제거했습니다. 하지만 요청 대상이 달라지는지는 확인하지 않았습니다. 2011년에는 Proxy-Authorization도 같은 헤더 옵션에서 읽도록 추가됐습니다.

수정 방식과 검증

작성자와 Jakub Zelenka는 새 URL을 해제하기 전에 먼저 파싱하도록 코드를 바꿨습니다. 이어 기존 URL과 새 URL의 스킴, 호스트, 포트를 비교합니다. 포트가 생략되면 HTTPS는 443, HTTP는 80으로 간주합니다. 출처가 달라지면 재귀 호출에 인증 헤더 제거 플래그를 전달하고, 한 번 켜진 플래그는 이후 리다이렉트에도 유지합니다. 따라서 다른 출처로 이동한 뒤 원래 호스트로 돌아와도 인증 헤더를 다시 보내지 않습니다.

수정된 wrapper는 Authorization, Cookie를 제거합니다. 프록시를 통하지 않는 요청에서는 Proxy-Authorization도 제거합니다. 사용자 정의 X-Custom 헤더는 그대로 전달하는 회귀 테스트를 추가했습니다. 작성자는 로컬 서버 두 대를 포트가 다른 주소에 띄우고, 두 번째 서버가 다시 자신에게 리다이렉트하도록 시험했습니다. PHP 8.5.10은 두 요청 모두에 Bearer SECRET을 보냈지만, 수정된 PHP 8.5 브랜치는 두 요청 모두에서 보내지 않았습니다.

헤더 제거 코드와 다른 클라이언트

기존 strip_header()는 헤더 이름을 strstr()로 찾아 첫 항목만 제거했습니다. 마지막 헤더를 지울 때 앞쪽 줄바꿈을 남겨 중복된 빈 줄을 만들기도 했습니다. 307·308 리다이렉트처럼 POST 본문을 유지하는 요청에서 Authorization 줄을 제거하면 헤더 블록이 잘려, 대상 서버가 잘못된 바이트를 본문으로 받을 수 있었습니다. 수정본은 줄 단위로 훑어 반복 헤더와 접힌 이어쓰기 줄, 콜론 앞에 공백이 있는 헤더를 처리하고, 앞의 줄바꿈도 함께 제거합니다.

curl은 2018년 CVE-2018-1000007 수정에서 기본적으로 원래 호스트에만 사용자 지정 Authorization 헤더를 유지하도록 했습니다. 다만 호스트 이름만 비교해 포트나 스킴이 달라도 헤더를 보냈습니다. 이 문제는 CVE-2022-27776으로 이어졌고, curl 7.83.0은 2022년 4월에 출처 비교를 강화했습니다. PHP 수정도 curl처럼 스킴·호스트·포트가 모두 같아야 인증 정보를 유지합니다. Go의 net/http는 원래 도메인과 같거나 하위 도메인일 때 일부 인증 헤더를 전달하며, Python requests는 호스트 변경 때 Authorization을 제거하되 표준 포트의 HTTP에서 HTTPS로 승격하는 경우는 허용합니다.

수정 버전은 PHP 8.2.34, 8.3.35, 8.4.26, 8.5.11이며 2026년 9월 24일 공개됐습니다. 비밀이 X-Api-Key처럼 자체 헤더에 들어 있다면 wrapper가 제거하지 않으므로 follow_location을 끄고 리다이렉트 대상 주소를 확인한 뒤 직접 따라가야 합니다.

Reddit 반응

  • @u/AlexandreDaubois — 오늘 배포한 PHP 보안 릴리스의 수정 작업을 공동으로 작성했습니다. 출처 확인 규칙을 두고 의견을 나눌 수 있습니다. Go의 ‘같은 도메인 또는 하위 도메인’ 규칙을 따르지 않고, 2022년 이후 curl처럼 스킴·호스트·포트를 비교합니다. 헤더를 한 번 제거하면 이후 리다이렉트가 원래 호스트로 돌아와도 다시 보내지 않습니다. 질문이 있으면 답하겠습니다.

원문: daubois.dev / 번역·요약: Trawling