[PortSwigger Academy Web Cache Deception] 을 수강하고 정리하였습니다.
#Web Caches
웹 캐시는 서버와 사용자 사이에 존재하는 시스템이다. 클라이언트가 정적 리소스를 요청하면, 요청은 가장 먼저 캐시로 향해진다. 만약 캐시가 리소스에 대한 카피가 없다면(Cache Miss), 요청은 응답하는 서버로 보내진다. 그 다음 응답은 사용자에게 전달되기 이전에 캐시로 전달되고, 캐시는 응답을 저장할지 결정하는 미리 구성된 일련의 룰(Cache Rule)을 이용하여 응답을 저장하거나 저장하지 않은 다음에 사용자에게 전달 된다.
만약 캐시가 응답을 저장했다면, 동일한 정적인 리소스에 대한 요청이 나중에 만들어질 때 캐시는 저장된 응답의 카피를 사용자에게 곧바로 제공한다(Cache Hit).
캐싱하는 것은 웹 콘텐츠를 전달하는 측면에서 점점 흔해지고 중요해지는데, 전 세계에서 제공되는 서버에 저장되어 있는 콘텐츠의 카피를 캐싱하는 데에 사용되는 Content Delivery Networks(CDNs)의 사용에서 특히 그렇다. CDNs는 데이터 이동 거리를 최소화함으로써 로드하는 시간을 줄여 사용자와 가장 가까운 서버로부터 콘텐츠를 제공하게 되어 전달 속도를 향상 시킨다.
Cache keys
캐시는 HTTP 요청을 받을 때, 곧바로 전달할 수 있는 캐싱된 응답이 존재하는지 혹은 원래의 서버로 곧바로 요청을 보내야만 하는지를 결정해야 한다. 캐시는 HTTP 요청의 요소로부터 cache key라는 것을 생성함으로써 이 결정을 내린다. 일반적으로 이는 URL 경로와 쿼리 파라미터를 포함하고 있으나, 헤더와 콘텐츠 타입과 같은 다른 다양한 요소를 포함하기도 한다.
만일 들어온 요청의 캐시 키가 이전 요청의 캐시 키와 매치된다면, 캐시는 그들을 동등하다고 보고 캐싱된 응답의 카피를 제공한다.
Cache rules
캐시 룰은 어떤 것이 캐싱되고 얼마나 오래 존재해야 하는지를 결정한다. 캐시 룰은 일반적으로 자주 바뀌지 않고 다수의 페이지에서 재사용되는 정적인 리소스들을 저장하기 위해 세팅된다. 동적인 콘텐츠는 사용자가 가장 최신의 데이터를 서버로부터 바로 얻을 수 있도록 하며 민감한 정보를 포함하곤 하기 때문에 캐싱되지 않는다. Web Cache Deception 공격은 캐시 룰이 적용되는 방법을 공격하기에 다양한 타입의 룰을 아는 것이 중요한데, 특히 요청의 URL 경로에서 정해져 있는 문자열을 기반으로 하는 경우를 포함한다.
예를 들어
- 정적인 파일 확장자 룰 :
.css
혹은.js
와 같이 요청된 리소스의 파일 확장자를 매치한다. - 정적인 디렉터리 룰 : 특정한 prefix로 시작하는 모든 URL 경로를 매치한다. 이는 주로
/static
이나/assets
와 같이 오로지 정적인 리소스만을 포함하고 있는 특정한 타겟 디렉토리를 위해 사용된다. - 파일 이름 룰 :
robots.txt
와favicon.ico
와 같이 웹 운영에서 글로벌하게 요청되고 거의 변하지 않는 특정한 파일 이름을 타겟 파일로 매치한다.
#Constructing a web cache deception attack
일반적으로 말하기를, 기본 web cache deception 공격은 다음과 같은 스텝을 포함하고 있다.
민감한 정보를 포함하는 동적인 응답을 리턴하는 타겟 엔드포인트를 찾아낸다. 렌더링된 페이지에서는 민감한 정보가 보이지 않을 수 있기 때문에 Brup Suite에서 응답을 조사한다. 원본 서버의 상태를 변경하는 요청은 일반적으로 캐싱 되지 않기 때문에
GET
,HEAD
,OPTIONS
메소드를 지원하는 엔드포인트에 포커싱한다.캐시와 원본 서버가 URL 경로를 파싱하는 방법 내에서 모순을 찾아낸다. 여기서 모순은 URL을 리소스에 매핑하기, 구분 기호 문자 처리, 경로 정규화와 같은 방법 내에서 존재할 수 있다.
모순을 이용하여 캐시를 속여 동적 응답을 저장하도록 하는 악성 URL을 만든다. 피해자가 URL에 접근할 때, 응답이 캐시에 저장된다. Burp를 이용해서 동일한 URL이 피해자의 데이터를 포함하고 있는 캐싱된 응답을 가져오도록 요청을 보낼 수 있다. 취약점을 숨길 수 있는 세션이나 유효하지 않은 로컬 데이터 없이 사용자를 리다이렉트 할 수 있기 때문에 브라우저에서 직접적으로 이를 하면 안된다.
Using a cache buster
취약점을 테스트하고 web cache deception 공격을 진행하는 동안, 우리가 보내는 각 요청이 다른 캐시 키를 가지고 있도록 해야한다. 그렇지 않으면, 테스트 결과에 영향을 끼치는 캐싱된 응답을 받을 수 있기 때문이다.
URL 경로와 어떠한 쿼리 파라미터든지 모두 일반적으로 캐시 키를 포함하고 있기 때문에, 쿼리 스트링을 경로에 추가하고 요청을 보낼 땨마다 이를 바꿈으로써 키를 바꿀 수 있다. Param Miner 확장자를 이용하여 이 과정을 자동화할 수 있다. 이를 하기 위해서는 먼저 해당 확장을 설치하고, 상단의 Param Miner > Settings 메뉴를 클릭하고, Add dynamic cachebuster를 선택하면 된다. 이제 Burp는 특정한 쿼리 스트링을 매 요청에 추가할 수 있다. 추가된 쿼리 스트링은 Logger 탭에서 확인할 수 있다.
Detecting cached responses
테스트를 하는 동안, 캐싱된 응답을 식별할 줄 아는 것이 중요하다. 이를 위해서는 응답 헤더와 응답 시간을 보면 된다.
다양한 응답 헤더가 응답이 캐싱 되었다는 것을 가리킨다. 예를 들어,
X-Cache
헤더는 응답이 캐시로부터 전달되었는지에 대한 정보를 제공한다.X-Cache: hit
: 응답이 캐시로부터 전달되었다.X-Cache: miss
: 캐시가 요청의 키에 대한 응답을 갖고 있지 않기에 원본 서버로부터 가져왔다. 대부분의 경우에는 그 다음에 캐시를 한다. 이를 확인하기 위해서는 요청을 다시 보내어 값이 hit로 업데이트 되었는지를 보면 된다.X-Cache: dynamic
: 원본 서버가 동적으로 콘텐츠를 생성했다. 일반적으로 이는 응답이 캐싱에 적합하지 않다는 것을 의미한다.X-Cache: refresh
: 캐싱된 콘텐츠의 기간이 만료되어 리프레시하거나 갱신이 필요하다.
Cache-Control
헤더에는 최대 수명max-age
이 0보다 더 큰public
과 같은 캐싱을 나타내는 지시문이 포함될 수 있다. 이는 리소스가 캐싱 가능하다는 것을 암시할 뿐이다. 또한 항상 캐싱을 나타내는 것이 아니며, 캐시가 이 헤더를 무시하는 경우도 있다.
만일 같은 응답에 대해 응답 시간에 큰 차이가 있다는 것을 알아내었다면, 이는 마찬가지로 빠른 응답이 캐시로부터 전달되었음을 이야기 한다.
#Explotiing static extension cache rules
캐시 룰은 .css
나 .js
와 같은 흔한 파일 확장자를 매칭함으로써 정적인 리소스를 타겟하곤 한다. 이는 대부분의 CDNs에서 기본 동작이다. 만일 캐시와 원본 서버가 URL 경로를 리소스에 매핑하거나 구분자를 사용하는 방식에 모순이 존재한다면, 공격자는 원본 서버에 의해 무시되지만 캐시에서는 보여지는 정적인 확장자를 갖는 동적인 리소스에 대한 요청을 만들 수 있다.
#Using path mapping discrepancies
Path mapping discrepancies
URL 경로 매핑은 URL 경로를 파일, 스크립트, 명령어 실행과 같이 서버의 리소스에 연관 시키는 과정을 말한다. 다양한 프레임워크와 기술에 의해 사용되는 다양한 범위의 매핑 스타일이 존재한다. 전통적인 URL 매핑과 RESTful URL 매핑이 흔한 매핑 스타일들이다.
전통적인 URL 매핑은 파일 시스템 내에 위치하는 리소스로 향하는 직접적인 경로를 나타낸다. 다음은 일반적인 예시이다.
http://example.com/path/in/filesystem/resource.html
http://example.com
은 서버를 가리킨다./path/in/filesystem/
은 서버의 파일 시스템에 있는 디렉터리 경로를 나타낸다.resource.html
은 접근 되어지는 특정한 파일이다.
대조적으로, REST 스타일의 URL들은 물리적인 파일 구조에 직접적으로 매칭되지 않는다. 이들은 파일 경로를 API의 논리적인 부분에 추상화한다.
http://example.com/path/resource/param1/param2
http://example.com
은 서버를 가리킨다./path/resource/
는 리소스를 나타내는 엔드포인트이다.param1
과param2
는 요청을 처리하기 위해 서버에 의해 사용되는 경로 파라미터이다.
캐시와 원본 서버가 리소소 URL 경로를 매핑하는 방법에서의 모순은 web cache deception 취약점을 야기할 수 있다.
http://example.com/user/123/profile/wcd.css
REST 스타일의 URL 매핑을 사용하는 원본 서버는 이 URL을
/user/123/profile
엔드 포인트에 대한 요청으로 해석할 수 있고,wcd.css
를 중요하지 않은 파라미터라고 생각하여 이를 무시하고 사용자123
에 대한 프로필 정보를 반환할 수 있다.전통적인 URL 매핑을 사용하는 캐시는 이를
/user/123
내의/profile
디렉터리에 위치한wcd.css
라는 이름을 가진 파일에 대한 요청으로 간주할 수 있다. 이는 URL 경로를/user/123/profile/wcd.css
로 해석한다. 만일 경로가.css
로 끝나는 요청에 대한 응답을 저장하도록 구성되어진 경우, 이는 마치 CSS 파일이었던 것처럼 프로필 정보를 캐싱하고 전달한다.
Exploiting path mapping discrepancies
원본 서버가 URL 경로를 리소스에 매핑하는 방법을 테스트하기 위해서는, 임의의 경로 세그먼트를 타겟 엔드포인트의 URL에 추가해야 한다. 만일 응답이 기본 응답과 동일한 민감한 데이터가 여전히 포함되어 있는 경우, 이는 원본 서버가 URL 경로를 추상화하고 추가된 세그먼트를 무시한다는 것을 나타낸다. 예를 들어, /api/orders/123
을 /api/orders/123/foo
로 수정해도 주문 정보가 반환되는 경우가 있다.
캐시가 URL 경로를 리소스에 매핑하는 방법을 테스트하기 위해서는, 정적인 확장자를 추가함으로써 캐시 룰에 매치되도록 경로를 수정해야 한다. 예를 들어, /api/orders/123/foo
를 /api/orders/123/foo.js
로 업데이트 해라. 만약 응답이 캐시되면, 1) 캐시가 정적인 확장자를 가진 전체 URL 경로를 해석한다는 것과 2) .js
로 끝나는 요청에 대한 응답을 저장하기 위한 캐시 룰이 존재한다는 것을 의미한다.
캐시는 특정한 정적인 확장자에 기반한 룰을 가지고 있을 것이기에 .css
, .ico
, .exe
를 포함한 다양한 범위의 확장자를 시도해볼 수 있다.
이제, 캐시에 저장되는 동적인 응답을 반환하는 URL을 만들어 볼 수 있다. 원본 서버가 다양한 엔드포인트에 대한 다양한 추상화 룰을 가지고 있기 때문에, 이 공격은 우리가 테스트한 특정 엔드포인트에만 국한된다.
Burp Scanner는 스캔 중 경로 매핑 모순에 의해 발생하는 web cache deception 취약점을 자동으로 찾아낼 수 있다. Web Cache Deception Scanner BApp을 사용하여 잘못 구성된 웹 캐시를 찾아낼 수도 있다.
🚩Lab: Exploiting path mapping for web cache deception
사용자 carlos
의 API 키를 찾아내는 문제이다. web cache deception 취약점을 이용할 수 있는 부분을 찾아보자.
우선 wiener
의 계정으로 로그인하고 /my-account
경로에 진입하면 로그인한 계정인 wiener
의 API Key를 확인할 수 있다. 이렇게 민감한 데이터가 노출되는 곳이 취약점을 공격할 수 있는 엔드포인트가 된다.
이제 burp suite에서 /my-account
경로에 임의의 하위 경로 /foo
를 추가하여 /my-account/foo
에 요청을 보내보자. 요청에 대한 응답에서 여전히 API 키가 전달되는 것을 확인할 수 있다. 이는 하위 세그먼트에 대해서 무시한다는 것을 알 수 있다. 그 다음으로 /foo
경로 대신 /foo.js
를 추가하여 /my-account/foo.js
에 요청을 보내보자.
여전히 API 키를 반환하는 것을 확인할 수 있을 뿐아니라 Cache-Control: max-age=30
필드와 X-cache: miss
필드를 확인할 수 있는 것으로 보아 해당 페이지가 캐싱되는 것을 알 수 있다.
30초 이내에 다시 같은 요청을 보내면 위와 같이 X-cache
필드가 hit
로 설정된 것을 확인할 수 있다. 이는 현재 응답이 서버로부터가 아닌 캐시에서 가져온 것임을 알 수 있다.
이 페이지는 원래 wiener
계정으로 로그인하여 /my-account
경로에 요청을 보내야 확인할 수 있는 페이지이지만 로그인 하지 않더라도 다른 사용자가 해당 경로 /my-account/foo.js
로 요청을 보내면 요청에 대한 응답을 서버가 아닌 캐시에서 가져오기 때문에 해당 페이지를 확인할 수 있다.
이와 같이 타겟 피해자인 carlos
가 /my-account/something.js
경로로 요청을 보내게 하면 /my-account
페이지가 캐싱되고, 이를 공격자가 경로로 요청을 보내면 해당 페이지를 확인할 수 있으며 최종적으로 API Key를 획득할 수 있다.
해당 랩에서 제공하는 exploit server를 활용하여 carlos가 url에 접속할 수 있도록 페이로드를 작성하자. 위와 같이 /my-account/foo.js
경로에 접속할 수 있도록 body에 script 태그를 추가한다. payload 저장 후 Deliver exploit to victim을 통해 전달한다.
바로 같은 경로에 요청을 보내면 캐싱된 페이지를 확인할 수 있고, carlos
의 API Key를 획득할 수 있다.
#Using delimiter discrepancies
구분자(delimiter)는 URL에서 서로 다른 요소 간의 경계를 특정한다. 구분자로 문자나 문자열을 사용하는 것이 일반적인데, 예를 들어 ?
는 URL과 쿼리 스트링을 구분하는 데에 사용된다. 그러나 URI RFC가 허용될 때, 다양한 프레임워크나 기술에서 변수가 발생할 수 있다.
캐시와 원본 서버가 문자나 문자열을 구분자로 사용하는 방식에서 발생하는 모순은 web cache deception 취약점을 야기할 수 있다.
예를 들어, /profile;foo.css
는 자바 스프링 프레임워크에서는 ;
문자를 행렬 변수로 알려진 파라미터를 추가하기 위해 사용된다. 따라서 자바 스프링을 사용하는 원본 서버는 ;
를 구분자로 해석하게 된다. /profile
뒤의 경로를 잘라내고 프로필 정보를 반환한다. 대부분의 다른 프레임워크에서는 ;
를 구분자로 사용하지 않는다. 따라서, 자바 스프링을 사용하지 않는 캐시는 ;
와 그 뒤의 모든 것을 경로의 일부로 해석할 가능성이 높다. 만일, 캐시가 .css
로 끝나는 요청에 대한 응답을 저장하는 규칙을 갖고 있다면, 이는 마치 CSS 파일인 것처럼 캐싱되어 프로필 정보를 전달할 수도 있다.
이처럼 다른 문자들 역시 프레임워크와 기술 사이의 비일관되게 사용된다. 아래의 3가지는, .
을 응답 형식을 지정하기 위한 구분자로 사용하는 Ruby의 Rails 프레임워크가 구동 중인 원본 서버에 대한 요청이다
/profile
- 이 요청은 사용자 프로필 정보를 반환하는 디폴트 HTML 포맷터에 의해 처리된다./profile.css
- 이 요청은 CSS 확장자로 인식된다. CSS 포맷터는 없기 때문에 요청은 받아들여지지 않고 에러를 반환한다./profile.ico
- 이 요청은.ico
확장자를 사용하는데, 이는 Ruby의 Rails에 의해서는 인식되지 않는다. 디폴트 HTML 포맷터가 요청을 처리하고 프로필 정보를 반환한다. 이러한 상황에서, 캐시가.ico
로 끝나는 요청에 대한 응답을 저장하기 위해 설정되어 있다면, 이는 마치 정적 파일인 것처럼 캐싱되어 프로필 정보를 전달할 수도 있다.
인코딩된 문자들이 때로는 구분자로 사용되기도 한다. 예를 들어, /profile%00foo.js
에서, OpenLiteSpeed 서버는 인코딩된 null 캐릭터 %00
을 구분자로 사용한다. 따라서 OpenLiteSpeed를 사용하는 원본 서버는 /profile
경로를 해석한다. 그러나 대부분의 다른 프레임워크는 %00
이 URL에 있다면 에러가 발생하는데, 캐시가 Akami 혹은 Fastly를 사용한다면 이는 %00
과 그 뒤에 오는 모든 내용을 경로로 해석한다.
Exploiting delimiter discrepancies
구분자 모순을 사용하여 캐시에 보여지는 경로에 정적인 확장자를 추가할 수 있지만 원본 서버에서는 볼 수 없다. 이를 하기 위해서는 캐시가 아닌 원본 서버에 의해 구분자로 사용되는 문자를 알아낼 필요가 있다.
첫 번째로, 원본 서버에 의해 구분자로 사용되는 문자를 찾는다. 임의의 문자열을 타겟 엔드포인트의 URL에 추가하는 방법으로 시작할 수 있다. 예를 들어, /settings/users/list
를 /settings/users/listaaa
로 바꿀 수 있을 것이다.
(만약 응답이 원래의 응답과 같다면, 이는 요청이 리다이렉트되고 있음을 알 수 있다. 따라서 테스트할 다른 엔드포인트를 찾아야한다.)
다음으로, 원래의 경로와 임의의 문자열 사이에 가능한 구분자 문자를 추가한다. /settings/users/list;aaa
와 같은 예시에서, 만일 응답이 기본 응답과 같다면 이는 ;
문자가 구분자로 사용되는 것이고 원본 서버는 이 경로를 /settings/users/list
로 해석한다는 것을 알 수 있다. 만일 경로에 대한 응답이 임의의 문자열과 일치한다면 이는 ;
문자가 구분자로 사용되지 않는다는 것이고 원본 서버는 이 경로를 /settings/users/list;aaa
로 해석한다는 것을 알 수 있다.
원본 서버에서 사용되는 구분자를 알아내는 데에 성공했다면, 이 구분자가 캐시에서도 사용되는지를 테스트해야 한다. 이를 위해서는 정적인 확장자를 경로의 끝에 추가해볼 수 있다.
만일 응답이 캐싱된다면, 이는 1) 캐시가 구분자를 사용하지 않고 정적인 확장자를 갖는 전체 URL 경로를 해석한다는 것이고 2) .js
로 끝나는 요청에 대한 응답을 저장하는 캐시 규칙이 존재한다는 것이다.
모든 ASCII 문자와 .css
, .ico
, .exe
와 같이 흔하게 사용되는 확장자를 모두 테스트해봐야 한다.
그리고 나서는, 정적인 확장자에 대한 캐시 규칙을 트리거하는 익스플로잇을 만들 수 있다. 예를 들어, /settings/users/list;aaa.js
페이로드에 대해 서버가 ;
를 구분자로 해석한다면 1) 캐시는 경로를 /settings/users/list;aaa.js
로 해석하고 2) 원본 서버는 경로를 /settings/users/list
로 해석한다는 것을 알 수 있다.
그러면 원본 서버는 캐시에 저장된 동적인 프로필 정보를 반환한다. 구분자는 일반적으로 각 서버 내에서 일관적이게 사용되기 때문에, 이를 이용하여 다른 다양한 엔드포인트를 공격할 수 있을 것이다.
몇몇의 구분자는 브라우저가 캐시에 요청을 전달하기 이전에 처리될 수 있다. 이는 어떤 구분자는 익스플로잇에 상요될 수 없다는 것을 의미한다. 예를 들어,
{
,}
,<
,>
와 같은 문자를 URL 인코딩하고,#
을 사용하여 경로를 잘라낸다. 만일, 캐시나 원본 서버가 이러한 캐릭터를 디코딩한다면, 익스플로잇에 인코딩된 버전을 사용하는 것이 가능할 수도 있다.
🚩Lab: Exploiting path delimiters for web cache deception
이번 랩은 이전 랩과 동일하게 web cache deception을 이용하여 carlos
계정의 API key를 찾아내면 된다. 이전 랩과 다른 점은 delimiter discrepancies를 이용하여 해당 API key가 있는 페이지를 캐싱하여 획득하면 된다.
우선 /my-account
경로에서 wiener
의 계정으로 로그인하면 해당 계정의 API key를 확인할 수 있다. 해당 페이지가 민감한 정보인 API key를 획득할 수 있는 페이지이기 때문에 이를 타겟 엔드포인트로 설정하여 요청 경로에 구분자를 포함하는 페이로드를 실행할 수 있다.
첫 번째 스텝으로 /my-account!foo.js
경로로 요청을 보내면 Not Found 라는 글자를 확인할 수 있는데, 이렇게 /my-account
와 임의의 정적인 확장자 파일인 foo.js
사이에 구분자를 추가하여 정상적인 응답을 반환하는 구분자를 찾는 것이다.
이를 위해 Burp의 Intruder 기능을 사용해보자. 위와 같이 /my-account
로 요청을 보내었던 패킷의 내용을 그대로 붙여넣고 구분자로 사용할 곳을 지정하여 구분자로 가능한 문자를 모두 대입하여 요청을 전송하는 것이다. PortSwigger에서 제공하는 delimiter list를 페이로드 리스트에 추가하고, 공격을 진행한다.
공격에 대한 결과를 보면, ;
문자와 ?
문자는 상태 코드 값이 200
인 것에 반해 나머지는 404
인 것을 확인할 수 있다.
여기서, ?
문자는 쿼리 스트링을 구분하기 위해 사용되기 때문에 정상적으로 동작한 것이지만 ;
문자는 비정상적으로 원래의 요청 패킷에 대한 응답과 동일한 응답을 반환하는 것을 확인할 수 있다. 이는 원본 서버에서 ;
를 구분자로 처리하기에 ;
뒤의 문자는 자르고 경로를 /my-account
로 해석한 것임을 알 수 있다.
이제 ;
문자를 구분자로 포함한 패킷을 전송하면 응답 패킷의 X-Cache
해더 값이 miss
인 것을 확인할 수 있고 한 번 더 요청을 하면 헤더 값이 hit
인 것을 확인할 수 있다. 이는 캐시가 ;
를 구분자로 처리하지 않고 ;foo.js
까지 모두 경로로 해석이 되어 응답을 캐싱한 것임을 알 수 있다. 이제 타겟 피해자 carlos
가 해당 페이지에 구분자와 정적인 확장자를 포함한 요청을 보내게 하여 carlos
의 API key가 포함된 페이지가 캐싱되게 하고 동일한 요청을 보내어 캐싱된 페이지를 조회하여 key를 얻어내면 된다.
Go to exploit server로 이동하여 carlos
에게 보낼 요청 패킷을 작성하자. 위와 같이 구분자 ;
와 이전에 이미 캐싱되었던 foo.js
문자열이 아닌 임의의 문자열을 사용하여 /my-account;abc.js
로 페이지 요청을 보내도록 body에 script를 추가한다.
이제 해당 패킷을 deliver하고 동일한 경로로 패킷을 요청하면 X-Cache
값이 hit
인 것을 확인할 수 있고, carlos
의 API key 값을 획득할 수 있다.
Delimiter decoding discrepancies
때때로 웹 사이트는 구분자와 같이 URL 내에서 특정한 의미를 가지는 문자를 포함한 데이터를 보내는 경우가 있다. 이러한 문자들이 데이터로 확실히 해석되기 위해서 인코딩된다. 그러나, 어떤 파서는 URL을 처리하기 전에 특정한 문자를 디코딩하기도 한다. 만약 이에 구분자가 디코딩되면, URL 경로가 잘리면서 구분자로 취급된다.
캐시와 원본 서버에서 구분자가 디코딩 되는 방식의 차이는 캐시와 원본 서버 모두 같은 문자를 구분자로 사용한다고 하더라도 URL 경로가 해석되는 방식에 모순이 발생할 수 있다. 예를 들어 URL 인코딩된 #
문자를 사용하는 /profile%23wcd.css
에서, 원본 서버는 %23
을 #
으로 디코딩한다. 이는 #
을 구분자로 사용하여 경로를 /profile
로 해석하고 프로필 정보를 반환한다. 그러나 캐시 역시 #
문자를 구분자로 사용하나, %23
을 디코딩 하지 않는다. 이는 경로를 /profile%23wcd.css
로 해석한다. .css
확장자에 대한 캐시 규칙이 존재한다면 응답은 저장될 것이다.
게다가 어떤 캐시 서버는 URL을 디코딩하여 이를 포함한 요청을 전달하는 경우도 있다. 이런 캐시 서버가 아닌 다른 캐시 서버는 가장 먼저 인코딩된 URL에 기반하여 캐시 규칙을 적용하고 나서, URL을 디코딩 한 다음에 이를 다음 서버로 전달한다. 이런 동작 역시 URL 경로를 해석하는 방식이 캐시와 원본 서버 사이에서 모순을 발생할 수 있다. 예를 들어 /myaccount%3fwcd.css
에서, 캐시 서버는 인코딩 된 /myaccount%3fwcd.css
경로를 기반으로 캐시 규칙을 적용하고 나서, .css
확장자에 대한 캐시 규칙이 존재하는지에 따라 응답을 저장할 것인지를 결정한다. 그 다음으로 %3f
를 ?
로 디코딩한 후에 이를 포함한 요청을 원본 서버로 전달한다. 그러나, 원본 서버에서는 /myaccount?wcd.css
요청을 받는다. 그리고 ?
문자를 구분자로 사용하여 경로를 /myaccount
로 해석한다.
결론적으로, 인코딩된 구분자를 사용하여 캐시에서 보는 경로에 정적 확장자를 추가하지만 원본 서버에서는 보지 않는 방식으로 디코딩 모순을 이용할 수 있다. %00
, %0A
, %09
와 같이 출력되지는 않지만 인코딩된 문자 범위에 존재하는 문자들을 이용할 수도 있다. 이러한 문자들이 디코딩 된다면, 이들을 URL 경로를 자르기 위해 사용할 수 있다.
Exploiting static directory cache rules
웹 서비가 특정한 디렉터리 내의 정적인 리소스들을 저장하는 것이 흔한 관례이다. 캐시 규칙은 /static
, /asstes
, /scripts
, /images
와 같이 특정한 URL 경로 접두사를 매칭함으로써 이러한 디렉터리를 타겟하곤 한다. 이러한 규칙들은 마찬가지로 web cache deception에 취약하다.
#Using normalization discrepancies
Normalization discrepancies
정규화 다양한 URL 경로 표현을 표준 형식으로 바꾸는 과정을 말한다. 이는 인코딩된 문자를 디코딩하고 dot-segments를 해석하는 것을 수반하지만, 파서마다 상당히 다르다.
캐시와 원본 서버가 URL을 정규화하는 방식에서의 모순은 공격자가 각 파서마다 다르게 해석되는 경로 탐색 페이로드를 만들어내는 것을 가능하게 해준다.
예를 들어 /static/..%2fprofile
에서, /
문자를 디코딩하고 dot-segments를 해석하는 원본 서버는 경로를 /profile
로 해석하고 프로필 정보를 반환한다. dot-segments를 해석하지 않거나 /
문자를 디코딩하지 않는 캐시는 경로를 /static/..%2fprofile
로 해석할 것이다. 캐시가 /static
접두사가 붙은 요청에 대한 응답을 저장하는 경우, 프로필 정보를 캐싱하고 제공한다.
위의 예시에서 볼 수 있듯이 경로 탐색 시퀀스에서 각 dot-segment는 인코딩될 필요가 있다. 그렇지 않으면, 피해자의 브라우저는 요청을 캐시에 전달하기 전에 이를 해석할 것이다. 따라서 공격 가능한 정규화 모순이 발생하려면 캐시나 원본 서버가 경로 탐색 시퀀스의 문자를 디코딩하고 dot-segments도 해석해야 한다.
Detecting normalization by the origin server
원본 서버가 URL 경로를 정규화하는 방법을 테스트하기 위해서는 경로 탐색 시퀀스와 경로의 시작 부분에 임의의 디렉터리를 사용하여 캐싱할 수 없는 리소스에 요청을 보내면 된다. 캐싱할 수 없는 리소스를 고르기 위해 POST
와 같은 비멱등적(결과가 바뀌는) 방식을 찾아야 한다.
예를 들어 /profile
을 /aaa/..%2fprofile
로 수정하면,
응답이 원래의 응답과 동일하고 프로필 정보를 반환할 때, 이는 경로가
/profile
로 해석되고 있고 원본 서버는/
를 디코딩하고 dot-segment를 해석한다는 것을 알 수 있다.응답이
404
에러 메시지를 반환하는 것과 같이 원래의 응답과 동일하지 않는다면, 이는 경로가/aaa/..%2fprofile
로 해석되고 있고 원본 서버가/
를 디코딩 않거나 dot-segment를 해석하지 않는다는 것을 알 수 있다.
정규화에 대해 테스트할 때, dot-segment 내의 두 번째
/
만을 인코딩 하는 것으로 시작해보아야 한다. 이는 몇몇의 CDNs이/
에 뒤따르는 정적 디렉터리 접두사를 일치시키기 때문에 중요하다. 마찬가지로 전체 경로 탐색 시퀀스를 인코딩하거나/
대신.
을 인코딩하는 것을 시도해볼 수 있다. 이는 때때로 파서가 시퀀스를 디코딩하는지 여부에 영향을 미칠 수 있다.
Detecting normalization by the cache server
캐시 서버가 경로를 정규화하는 방법을 테스트하기 위해서는 다양한 방법을 사용할 수 있다. 정적인 디렉터리를 식별하는 것부터 시작할 수 있다. 흔하게 사용되는 정적 디렉터리 접두사와 캐싱된 응답을 갖고 있는 요청을 찾는데, 2xx 응답, 스크립트, 이미지, CSS MIME 타입의 메시지를 힌트로 하여 정적인 리소스에 포커싱할 수 있다.
이후에 캐싱된 응답을 갖고 있는 요청을 선택하고 다시 경로 탐색 시퀀스와 정적 경로의 시작 부분에 있는 임의의 디렉터리와 함께 요청을 보낸다. 캐싱되었다는 근거를 포함하고 있는 응답을 갖고 있는 요청을 선택한다.
예를 들어 /aaa/...%2fassets/js/stockCheck.js
에서,
- 응답이 더 이상 캐싱되지 않는 경우는 캐시가 엔드포인트에 매핑되기 이전에 경로를 정규화하지 않을 것이라는 것을 의미한다. 이는
/assets
접두사에 근거한 캐시 규칙이 존재한다는 것을 알 수 있다. - 응답이 여전히 캐싱되는 경우에는 캐시가 경로를
/assets/js/stockCheck.js
로 정규화한다는 것을 의미한다.
두 경우 모두, 파일 확장자에 기반하는 등 다른 캐시 규칙으로 인해 응답이 캐싱될 수 있다. 캐시 규칙이 정적 디렉터리 기반인지를 확인하기 위해서는 디렉터리 접두사 뒤의 경로를 임의의 문자열로 바꿔야 한다. 예를 들어 /assets/aaa
에서 응답이 여전히 캐싱된다면, 캐시 규칙이 /assets
접두사에 기반한다는 것을 확인할 수 있다. 만일 응답이 캐싱되는 것으로 보이지 않는다면, 이는 때로는 404
응답이 캐싱되지 않는 경우도 있기 때문에 반드시 정적인 디렉터리 캐시 규칙을 배제하는 것이 아니다.
익스플로잇을 시도하지 않고 캐시가 dot-segments를 디코딩하고 URL 경로를 디코딩하는지 여부를 확실하게 판단하지 못할 수도 있다.
Exploiting normalization by the origin server
원본 서버가 인코딩된 dot-segments를 해석하나 캐시는 그렇지 않는 경우, 다음 구조를 따라 페이로드를 만들어냄으로써 모순을 익스플로잇 시도할 수 있다.
/<static-directory-prefix>/..%2f<dynamic-path>
예를 들어 /assets/..%2fprofile
페이로드를 보면, 캐시는 경로를 /assets/..%2fprofile
로 해석하고 원본 서버는 /profile
로 해석한다. 이에 원본 서버는 캐시에 저장된 동적 프로필 정보를 반환한다.
🚩Lab: Exploiting origin server normalization for web cache deception
이전 랩과 같은 환경의 문제이다. 우선 Burp의 Intruder를 통해 원본 서버에서 처리하는 delimeter가 무엇인지 찾아낸다. 여기서는 ?
문자만을 delimiter로 처리하는 것을 알게 되었다. 그러나 해당 페이지는 캐싱되지 않는 것을 확인하였고, 캐시 서버에 의해 캐싱되는 엔드포인트를 찾아야 한다.
서비스의 다른 페이지를 둘러보다가 /resources/images/avatarDefault.svg
라는 요청을 보내는 것을 확인하였다. /resources
라는 디렉터리에 정적인 파일들을 모아놓는 것으로 추정할 수 있고 해당 요청에 대한 응답 헤더에 X-Cache
헤더가 있는 것으로 보아 해당 페이지는 캐싱되는 것을 알 수 있었다.
/resources
경로를 타겟으로 하여 원본 서버와 캐시 서버가 URL 정규화를 시키는 방식을 알아보아야 한다. /resources/..%2fmy-account
경로로 요청을 보내자, 응답으로 /my-account
페이지를 얻는 것을 확인할 수 있고 본래 /my-account
로 요청을 보낼 때의 응답 패킷의 헤더에는 X-Cache
가 없었으나 이 요청에 대한 응답 패킷의 헤더에는 X-Cache
가 존재하는 것으로 보아 캐시 서버는 경로를 /resources/..%2fmy-account
로 해석하고 있는 것을 알 수 있다. 여기서 원본 서버와 캐시 서버의 URL 정규화 과정에서 모순이 발생한다는 사실을 이용할 수 있다.
이제 Go to exploit server 탭으로 이동하여 타겟 피해자인 carlos
가 페이지를 요청할 수 있도록 패킷을 작성하자. 피해자가 /resources/..%2fmy-account
로 요청을 보내되, 이미 캐시 서버에 캐싱된 URL을 피하기 위하여 delimiter인 ?
를 추가하여 임의의 URL을 새롭게 만들면 된다./resources/..%2fmy-account?qwe
경로로 요청을 보내도록 하여 carlos
의 API key가 담긴 페이지를 캐시 서버가 캐싱하도록 하고 해당 경로로 다시 요청을 보내면 X-Cache
값이 miss
에서 hit
로 바뀌며 API key를 획득할 수 있다.
Exploiting normalization by the cache server
만일 캐시 서버가 인코딩된 dot-segments를 해석하지만 원본 서버는 그렇지 않는 경우, 다음 구조에 따라서 페이로드를 구성함으로써 모순을 익스플로잇 시도할 수 있다.
/<dynamic-path>%2f%2e%2e%2f<static-directory-prefix>
캐시 서버에 의한 정규화를 익스플로인할 때, 경로 탐색 시퀀스 내의 모든 문자들을 인코딩해야 한다. 인코딩된 문자들을 사용하는 것이 구분자를 사용할 때 예상치 못한 동작을 방지할 수 있고, 또한 캐시는 디코딩된 것들을 다룰 것이기 때문에 정적 디렉터리 접두사가 뒤이어 나오는 인코딩 되지 않은
/
를 가질 필요가 없다.
이러한 상황에서, 경로 탐색만으로는 익스플로잇하기에 충분하지 않다. 예를 들어, 캐시 서버와 원본 서버가 페이로드 /profile%2f%2e%2e%2fstatic
를 해석하는 방법을 보자. 캐시는 경로를 /static
으로 해석하고, 원본 서버는 경로를 /profile%2f%2e%2e%2fstatic
으로 해석한다. 따라서 원본 서버는 프로필 정보 대신 에러 메시지를 반환할 가능성이 크다.
이 모순을 익스플로잇하기 위해서는 마찬가지로 원본 서버에서는 사용하지만 캐시 서버에서는 사용하지 않는 구분자를 식별할 필요가 있다. 동적인 경로 다음으로 가능한 구분자를 추가함으로써 테스트 해보아야 한다. 원본 서버가 구분자를 사용하는 경우, URL 경로가 잘리게 되고 동적인 정보를 반환할 것이다. 캐시 서버가 구분자를 사용하지 않는 경우, 경로가 올바르게 해석되고 응답이 캐싱될 것이다.
예를 들어 원본 서버에서 ;
를 구분자로 사용할 때 페이로드 /profile;%2f%2e%2e%2fstatic
에서, 캐시는 경로를 /static
으로 해석하고 원본 서버는 /profile
로 해석한다. 이에 원본 서버는 캐시에 저장된 동적 프로필 정보를 반환한다.
🚩Lab: Exploiting cache server normalization for web cache deception
이번 랩 역시 이전 랩들과 같은 유형의 문제이다. 우선 wiener
의 계정을 로그인하고 API key가 존재하는 동적 페이지 /my-account
에 접속한다.
캐시 서버와 원본 서버가 어떻게 정규화를 진행하는지를 알아보기 위하여 이전 랩에서 사용한 경로 /resources/..%2fmy-account
를 요청하여 본다. 응답으로 404
에러를 확인할 수 있는데 이는 원본 서버가 정규화 과정에서 dot-segment와 디코딩을 진행하지 않는다는 것을 알 수 있다. 또한 응답 패킷의 헤더에 X-Cache
값 역시 없는 것으로 보아 캐시 서버 역시 정규화 과정이 원본 서버와 같이 dot-segment와 디코딩을 진행하지 않는다는 것을 알 수 있다.
이번에는 /my-account
경로에서 정규화 테스트를 해볼 수 있는데, 요청 경로를 /<dynamic-path>%2f%2e%2e%2f<static-directory-prefix>
구조에 맞추어 /my-account
에서 /my-account%2f%2e%2e%2fresources
로 변경하여 요청하여 본다. 마찬가지로 응답 패킷으로 404
에러를 확인할 수 있는데 이는 원본 서버가 정규화 과정에서 dot-segments와 디코딩 과정을 진행하지 않는다는 사실을 알 수 있다. 그러나, X-Cache
헤더를 발견할 수 있고 재요청 시에 miss
값에서 hit
값으로 변경되는 것으로 보아 캐시 서버는 정규화 과정에서 dot-segments와 디코딩 과정을 진행한다는 사실을 알 수 있다.
그렇다면 원본 서버가 정규화 시에 해석하지 않는 부분을 URL 경로에서 자를 수 있도록 인코딩된 문자열 앞에 가능한 구분자를 찾을 필요가 있다. 이는 Burp에서 Intruder 기능을 이용하여 /my-account$delimiter$aaa.js
경로로 요청을 보내어 #
과 ?
, %23
, %3f
문자들이 구분자로서 해석되는 것을 알 수 있다.
이제 다음 4가지 조합의 경로를 요청할 수 있다.
/my-account#%2f%2e%2e%2fresources
/my-account?%2f%2e%2e%2fresources
/my-account%23%2f%2e%2e%2fresources
/my-account%3f%2f%2e%2e%2fresources
위의 요청 중에서 응답 패킷이 200
값을 가지며 X-Cache
헤더를 가지는 요청은 %23
을 구분자로 사용했을 때 뿐이다. 이는 URL이 해석될 때 캐싱 서버에서는 ?
와 %3f
를 쿼리 문자열을 경로로 포함하지 않고, #
은 일반적으로 서버로 전달되지 않기 때문에 경로 탐색에 사용할 수 없는 것이다. 따라서 %23
을 경로의 일부로 처리하여 캐싱하기에 /my-account%23/../../resources
를 그대로 캐싱 경로로 저장하게 되는 것이다.
이제 Go to exploit server 탭으로 이동 후 패킷의 내용을 해당 경로로 타겟 피해자 carlos
가 접속하도록 하여 API key가 담긴 페이지가 캐싱되도록, resource
경로 뒤에 ?
와 임의의 문자열을 추가하여 전달하자.
전달 후에 동일한 경로로 접속하면 X-Cache
값이 hit
로 되어 캐싱된 것을 확인할 수 있고 carlos
의 API key 값을 획득할 수 있다.
#Exploiting file name cache rules
robots.txt
, index.html
, favicon.ico
와 같은 특정 파일들은 웹 서버에서 흔하게 찾아볼 수 있다. 이들은 자주 변하지 않기 대문에 캐싱되는 가능성이 크다. 캐시 규칙은 파일 이름 문자열을 매칭함으로써 이러한 파일들을 타겟한다. 파일 이름 캐시 규칙이 존재하는지 알기 위해서는 가능한 파일에 대한 GET
요청을 보내고 응답이 캐싱되었는지 확인하면 된다.
Detecting normalization discrepancies
원본 서버가 URL 경로를 어떻게 정규화하는지를 테스트하기 위해서는 정적 디렉터리 캐시 규칙을 위해 앞서 사용했던 같은 방식을 사용하면 된다.
캐시 서버가 URL 경로를 어떻게 정규화하는지를 테스트하기 위해서는 경로 탐색 시퀀스를 포함하고 파일 이름 이전에 임의의 디렉터리를 포함한 요청을 보내면 된다. 예를 들어 /aaa%2f%2e%2e%2findex.html
에서, 응답이 캐싱되었다면 이는 캐시가 경로를 /index.html
로 정규화하고 있다는 것을 나타내고, 응답이 캐싱되지 않았다면 이는 캐시가 /
를 디코딩하지 않고 dot-segment를 해석하지 못하여 /aaa%2f%2e%2e%2findex.html
로 해석하지 않는다는 것을 의미한다.
Exploiting normalization discrepancies
요청이 정확한 파일 이름에 매칭되었을 경우에만 응답이 캐싱되기 때문에, 캐시 서버가 dot-segments를 해석하나 원본 서버는 그렇지 않는 모순만을 익스플로잇할 수 있을 것이다. 단지 정적 디렉터리 캐시 규칙에 대한 방법에서 정적 디렉터리 접두사를 파일 이름으로 바꾸어 익스플로잇할 수 있다.
#Preventing web cache deception vulnerabilities
web cache deception 취약점을 방지하기 위해서는 다양한 스텝을 밟을 수 있다.
동적인 리소스를 마킹하기 위해 항상
Cache-Control
헤더를 사용하고no-store
과private
와 같은 지시어로 설정한다.캐시 규칙이
Cache-Control
헤더를 오버라이드 하지 않게 하기 위해 CDN 설정을 구성한다.CDN이 web cache deception 공격에 대해 제공하는 모든 보호 기능을 활성화 시킨다. 많은 CDN은 응답의
Content-Type
가 요청의 URL 파일 확장자를 매칭하는지 확인하는 캐시 규칙을 설정하는 기능을 제공한다. 예를 들어, Cloudflare의 Cache Deception Armor가 있다.원본 서버와 캐시 서버가 URL 경로를 해석하는 방식 사이에서 어떠한 모순도 존재하지 않도록 검증한다.