보안 계층 : Origin, SOP, CORS, CSP

보안 계층

Origin(출처)

origin(출처)는 접근할 때사용하는 URL의 스킴(프로토콜), 호스트(도메인), 포트로 정의되는데, 두 객체의 스킴, 호스트, 포트가 모두 일치하는 경우 같은 출처를 가졌다고 말합니다.

 

https://www.test.com:80/main/search?type=blog&postnum=50#security

scheme Domain Name Port        File Path                              Parameters      Anchor

 

Scheme = 프로토콜

Host Address = Domain Name or IP Address

Anchor = Fragment = 책갈피 = 해당 문서 내에서 특정위치

 

Origin = Scheme + Host Address + Port

Same Origin = 동일 출처

 

[예제]

  • http://example.com/app1/index.html
  • http://example.com/app2/index.html

=> 기본 포트 80을 사용하므로 포트번호가 생략되어 있습니다.

=> Scheme과 Domain Name이 동일하기 하므로 두 개의 index.html은 동일 출처입니다.

 

  • http://example.com:80
  • http://example.com 

=> 하나는 포트번호 80을 명시하였고, 하나는 기본 포트(80)를 생략했기 때문에 동일 출처로 판단할 수 있습니다.

 

  • http://example.com/app1
  • https://example.com/app2

=> Scheme이 일치하지 않으므로 동일 출처가 아닙니다.

 

  • http://example.com
  • http://www.example.com
  • http://myapp.example.com

=> Domain Name이 다르기 때문에 동일 출처가 아닙니다.

 

  • http://example.com
  • http://example.com:8080

=> 포트 번호가 다르기 때문에 동일 출처가 아닙니다.

 

[참고문서] https://developer.mozilla.org/ko/docs/Glossary/Origin


SOP(Same Origin Policy; 동일 출처 정책)

어떤 origin에서 불러온 문서나 스크립트가 다른 origin에서 가져온 리소스와 상호작용할 수 있는 방법을 제한하는 중요한 보안 메커니즘입니다.

[예제]

http://www.test.com/index.html 

=> origin은 http://www.test.com 

<img src="http://www.abc.com/myimage.gif" />

<script src="http://www.abc.com/lib.js"></script>

=> 위의 코드도 origin이 다르지만 가져와서 쓸 수 있습니다.

=> 일반적으로 웹 애플리케이션은 출처가 다른 리소스를 사용할 수 있습니다.

=> 단, Javascript 또는 Ajax를 이용하여 리소스를 가져오는 경우, 동일 출처에서 가져온 것만 사용할 수 있도록 제한합니다.

 

[참고문서] https://developer.mozilla.org/ko/docs/Web/Security/Same-origin_policy

 

[같은 출처 이미지를 가져와서 출력]

#1 Bee-Box 가상머신 cmd에서 다음 코드를 입력합니다.

$ cd /var/www/bWAPP

$ sudo gedit sop.html

=> sop.html을 작성할 수 있는 창이 생깁니다.

 

#2 다음 코드를 sop.html에 작성합니다.

<html>
	<head>
		
	</head>
	<body>
		<img src="/bWAPP/images/bee_1.png" />
		<img src="http://beebox/bWAPP/images/bee_1.png" />
	</body>
</html>

 

#3 내 PC 크롬에서 http://beebox/bWAPP/sop.html으로 이동합니다.

=> 문서의 주소는 http://beebox/bWAPP/sop.html

=> 첫번째 이미지 주소는 /bWAPP/images/bee_1.png => http://beebox/bWAPP/images/bee_1.png

=> 두번째 이미지 주소는 http://beebox/bWAPP/images/bee_1.png

=> 첫번째와 두 번째 이미지의 출처가 문서의 출처와 동일

=> 따라서 이미지가 모두 출력됨

 

[다른 출처 이미지를 가져와서 출력]

#4 다음 코드를 sop.html에 작성합니다.

<html>
	<head>
		
	</head>
	<body>
		<h1>Same Origin O</h1>
		<img src="/bWAPP/images/bee_1.png" />
		<img src="http://beebox/bWAPP/images/bee_1.png" />
		<hr/>
		<h1>Same Origin X</h1>
		<img src="http://victim:8080/WebGoat/images/logos/owasp.jpg" />
		<img src="/WebGoat/images/logos/owasp.jpg" />
	</body>
</html>

 

#5 내PC 크롬에서 http://beebox/bWAPP/sop.html으로 이동합니다.

=> 세번째 이미지 주소는 http://victim:8080/WebGoat/images/logos/owasp.jpg

      웹은 기본적으로 출처가 다른 자원을 사용할 수 있기 때문에

      문서(sop.html)의 출처와 다름에도 이미지를 가져와서 출력합니다.

=> 네번째 이미지 주소는 /WebGoat/images/logos/owasp.jpg 

     출처가 명시되어 있지 않아서 문서의 출처를 상속받아 리소스를 요청하게 되는데

      http://beebox/WebGoat/images/logos/owasp.jpg 에는 이미지가 없으므로 이미지 요청에 실패합니다.

 

[javascript를 사용하여 이미지 출력]

#6 다음 코드를 sop.html에 작성합니다.

<html>
	<head>
		
	</head>
	<body>
		<h1>Same Origin O</h1>
		<img src="/bWAPP/images/bee_1.png" />
		<img src="http://beebox/bWAPP/images/bee_1.png" />
		<hr/>
		<h1>Same Origin X</h1>
		<img src="http://victim:8080/WebGoat/images/logos/owasp.jpg" />
		<img src="/WebGoat/images/logos/owasp.jpg" />
		<hr/>

		Image from http://beebox <img id="bimage" src="#" />
		<br/>
		Image from http://victim:8080 <img id="wimage" src="#" />
		<br/>
		<button onclick="loadImage('bimage','http://beebox/bWAPP/images/bee_1.png')">Load BeeBox Image</button>
		<button onclick="loadImage('wimage','http://victim:8080/WebGoat/images/logos/owasp.jpg')">Load WebGoat Image</button>

		<script>
			function loadImage(id, url) {
				const xhr = new XMLHttpRequest();
				xhr.onload = function() {
					if (xhr.status === 200) {
						const img = document.getElementById(id);
						img.src = URL.createObjectURL(xhr.response);					
					}else {
						console.error('Fail to load image', xhr.statusText);
					}
				};
		
				xhr.open("GET", url, true);
				xhr.responseType = 'blob';
				xhr.send();
			}
		</script>


	</body>
</html>

=> 이미지가 출력되는 곳이 두 군데이고, 버튼을 눌러 이미지를 출력하는 방식

=> 통신을 하기 위해 XMLHttpRequest 객체를 생성

=> GET방식으로 url주소에 있는 리소스를 가지고 옴. 여기서 가지고 오는 파일은 바이너리파일(blob)

 

#7 내PC 크롬에서 http://beebox/bWAPP/sop.html으로 이동합니다.

(1) Load BeeBox Image 클릭

Load BeeBox Image 버튼 클릭
bee_1.png header

=> 위의 bee_1.png는 Type이 pnd이고, http 브라우저를 통해서 이미지를 요청해서 가져온 것입니다.

=> 아래의 bee_1.png는 Type이 xhr이고, XMLHttpRequest라는 자바스크립트의 비동기 통신방식으로 직접적으로 가져온 것입니다.

=> bee_1.png의 Request URL을 보면 문서의 출처와 동일함을 볼 수 있다.

 

(2) Load WebGoat Image 클릭

Load WebGoat Image 버튼 클릭
owasp.jpg header
console 메시지 확인

출처가 다른 리소스는 Javascript에서 접근할 수 없습니다.

=> SOP 규약에 따르면 Javascript를 이용해서 리소스를 요청할 때는 동일 출처 리소스만 사용이 가능합니다.

=> 즉, owasp.jpg 파일을 사용할 수 없습니다.
=> 응답헤더가 있어야 SOP 제약을 풀어줄 수 있는데, 이것을 CORS라고 합니다.


CORS(Cross-Origin Resource Sharing; 교차 출처 자원 공유)

SOP 완화정책이라고 할 수 있는 CORS는 서비스를 제공하는 사이트(Javascript를 이용해서 요청하는 사이트)에서 Access-Control-Allow-Origin 응답 헤더를 통해 리소스 사용 여부를 지정합니다. 추가 http 헤더를 사용하여, 한 origin에서 실행 중인 웹 애플리케이션이 다른 origin의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다.

자바스크립트를 사용하여 요청하는 사이트에서 응답을 주는데 추가 헤더를 포함하여 응답을 주게 되면, 그때 받은 리소스를 읽어 들여 사용할 수 있게 됩니다.

 

단순요청(Simple Request)

=> preflight request를 거치지 않고, 바로 내가 원하는 리소스를 요청하는 것

=> 메소드가 GET, HEAD, POST일 때 

=> 요청헤더에 Accept, Accept-Language, Content- Language, Content-Type만 들어가야함

=> Content-Type이 application/x-www-form-urlencoded, multipart/form-data, text/plain 이 세 가지 중 하나일 것

=> 이런 범주의 요청이 자바스크립트를 통해 발생될 경우, preflight 없이 바로 원하는 리소스를 요청할 수 있습니다.

 

preflight request는 뭘까? 예를 들어, 내가 자바스크립트를 통해 요청을 하는데 이런 형태의 요청을 보내면 너가 나한테 CORS 설정해서 줄 수 있는지를 먼저 물어보는 작업입니다.

 

프리플라이트 요청(Preflighted  Request)

=> 자바스크립트를 통해 내가 요청한 것이 받아들여지는지 아닌지를 사전검증하는 요청

=> 브라우저가 알아서 처리할 것임

 

[참고문서] https://developer.mozilla.org/ko/docs/Web/HTTP/CORS


CSP(Content Security Policy; 컨텐츠 보안정책)

CSP는 XSS 공격을 방어하기 위한 브라우저 보안정책입니다. 개발자가 작성한 스크립트 코드와 공격자가 작성한 스크립트 코드를 구분하기 위한 방법 중 하나입니다.

=> 개발자가 작성한 스크립트는 브라우저가 실행하면 되는 것이고, 공격자가 작성한 것은 차단하면 됩니다.

 

개발자가 작성한 스크립트 코드를 어떻게 알 수 있을까?

응답 헤더에 Content Security Policy 헤더를 통해서 제공되는 정보를 이용해 판단할 수 있습니다.

 

#1 아래 사이트로 이동합니다.

https://content-security-policy.com/browser-test/

 

Content Security Policy Browser Test

Content Security Policy Browser Test JavaScript CSP Browser Test CSP Level 1 Note this test requires that you have JavaScript Enabled CSP Supported If you can read this, then the inline JavaScript below this line did not execute. //if CSP is supported this

content-security-policy.com

=> 브라우저 버전에 따라서 CSP가 제공하는 범주가 달라집니다.

 

#2 F12를 눌러 console 창을 보면 다음과 같은 오류가 뜹니다.

=> CSP에 어긋나기 때문에 인라인 스크립트 실행이 거부되었다.

 

#3 F12를 눌러 Network를 보면 CSP를 볼 수 있습니다.

=> CSP 헤더가 설정되어 있으면, 해당하는 이 문서에 있는 리소스를 사용하고자 할 때 이 규약에 맞는지 확인해야함

=> CSP를 확인해보면 script-src는 self만 가능하다. 즉, 동일한 origin인 경우만 사용 가능하다라는 의미이다.

=> 이 문서의 출처와 내가 사용하려는 리소스의 출처가 동일해야 사용 가능하다.

=> googletagmanager, twitter, syndication 등 CSP에 명시된 사이트에서 가져오는 것은 사용 가능하다.

 

스크립트를 넣는 방법

스크립트를 넣는 방법

External : <script src='문서 밖의 스크립트 파일 주소'></script>

Internal : <script>스크립트 코드</script>

Inline : <img onclick="스크립트 코드"/>

 

보안적으로는 External이 가장 안전합니다.

Internal과 Inline은 문서 내에 있기 때문에 개발자가 만든 것인지 공격자가 만든 것인지 구분하기 어렵습니다.

 

default-src 'none';

...

font-src fonts.gstatic.com cdnjs.cloudflare.com;

img-src 'self' syndication.twitter.com;

...

=> 이렇게 세미콜론으로 나뉘어져있는 부분씩을 directive(지시), 지시문이라고 합니다.

=> 폰트는 fonts.gstatic.com cdnjs.cloudflare.com 이곳에서 가져온 값만 허용한다.

=> 이미지는 'self' syndication.twitter.com 이곳에서 가져온 값만 허용한다. 

=> 보라색 선에서는 파란색 선에서 가져온 값만 허용한다

=> 노란색 선 : 스크립트 코드의 해쉬를 정의

 

#4 소스코드 내부에 포함되어 있는 스크립트 코드의 해쉬 값을 추출하여 비교합니다.

 

1번 해쉬값을 추출하면 다음과 같습니다.

1번 해쉬값 추출

추출된 해쉬 값과 Internal 스크립트 코드의 해쉬 정의를 비교해보면 해쉬 값이 다른 것을 확인할 수 있습니다.

=> CSP에 정의되어 있는 해쉬에 포함되지 않기 때문에 해당 스크립트 코드는 실행되지 않습니다.

 

2번 해쉬값을 추출하면 다음과 같습니다.

2번 해쉬 값 추출

추출된 해쉬 값과 nternal 스크립트 코드의 해쉬 정의를 비교해보면 해쉬 값이 같은 것을 확인할 수 있습니다.

=> CSP에 정의되어 있는 해쉬에 포함되기 때문에 해당 스크립트 코드는 실행됩니다.

 

[참고문서] https://developer.mozilla.org/ko/docs/Web/HTTP/CSP