CORS Bypass via dot

Origin 헤더와 ACAO(Access-Control-Allow-Origin) 헤더는 Cross-Origin 관계에서 데이터를 전달하고 수신하기 위한 헤더로 SOP(Same-Origin Policy)를 공식적으로 우회하기 위한 헤더입니다. 일반적으로 CORS라고 통용되어 부르며, 이는 JSON HijackingCSRF 취약점에 큰 접점을 가지고 있습니다.

오늘은 CORS 규칙에서 개발자가 쉽게 할 수 있는 실수와 이를 식별하는 방법에 대해 이야기하려고 합니다.

Origin Validation

Origin 헤더를 기반으로 ACAO 헤더를 내려주는 경우 해당 Origin에서 데이터를 통제할 수 있는 권한을 가지게 됩니다. 이 경우 브라우저가 Javascript로 Response를 통제할 수 있게 허용해주는데 무분별하게 허용한다면 악의적인 사이트에서 사용자의 정보 등을 가로챌 수 있기 때문에 우리는 Origin 헤더를 보고 요청의 출처를 검사하고, 우리가 예측 가능하고 신뢰하는 도메인에서만 이를 허용해줍니다.

Origin: trusted.com (O)
Origin: attacker.com (X)

그리고 보통 이러한 Origin 검사 로직은 여러 케이스를 처리해야하기 때문에 정규표현식을 기반으로 많이 작성합니다.

config.middleware.insert_before 0, Rack::Cors do
 allow do
   origins /^https:\/\/[0-9]{1,6}\.apps\.trusted\.com/
   resource '*', headers: :any, methods: %i[get post head]
 end 
end
Origin: https://123.apps.trusted.com (O)
Origin: https://123.google.com (X)

Dot Mistake

dot(.)은 정규표현식에서 개행문자를 제외한 모든 문자를 매치하는 메타문자입니다. 그러나 종종 정규표현식을 사용하다 보면 dot(.)의 존재를 잊게됩니다. 자연스럽게 도메인 주소를 집어넣게 되고 아래와 같은 코드가 작성될 수 있습니다.

config.middleware.insert_before 0, Rack::Cors do
 allow do
   origins /^https:\/\/[0-9]{1,6}.apps.trusted.com/
   resource '*', headers: :any, methods: %i[get post head]
 end 
end

언뜻 보기엔 문제가 없어 보이지만, dot의 존재로 인해 apps[임의문자]trusted.com과 같이 dot 자리에 임의 문자가 들어간 Origin도 정규표현식을 통과하게 됩니다.

Origin: https://123.apps.trusted.com (O)
Origin: https://123.google.com (X)
Origin: https://111.apps1trusted.com (O)

결과적으로 apps1trusted.com 이란 도메인을 소유하게 되면 Origin 검사 정책을 무력화 시키고 JSON Hijacking 같은 공격을 성공할 수 있게 됩니다.

How to Check

방법은 간단합니다. Origin 체크 시 dot 부분에 임의 문자를 넣어보는 형태로 쉽게 체크할 수 있습니다.

Origin: hahwul.com (O)
Origin: google.com (X)
Origin: abcd.hahwul.com (O)

Origin: hahwulacom (O)
Origin: hahwulbcom (O)
Origin: abcdahahwul.com (O)
Origin: abcdbhahwul.com (O)