홈페이지 취약점 분석 이야기 파일 지도 사진 깨알






>> 목록보이기
#X-Content-Type-Options #nosniff #HTTP 헤더 #Content-Type 헤더 #MIME Type #MIME형식 #시험페이지 #자바스크립트 파일 삽입 방어

MIME형식 해석 제한... X-Content-Type-Options: nosniff 헤더

Microsoft에서 제안하는 확장 헤더이며 웹서버가 보내는 MIME 형식 이외의 형식으로 해석을 확장하는 것을 제한하는 크로스사이트스크립트 방어법이다. MS Internet Explorer 9부터 지원한다. 의미는 간단하다. CSS MIME 형식("text/css")을 가지지 않는 파일은 웹 브라우저가 CSS로 사용하지 않아야 한다. JavaScript MIME 형식("text/javascript" 또는 "application/javascript")을 가져야만 JavaScript로 해석해야 한다는 의미이다. 따라서 이 방어법은 브라우저 의존적이다. 글을 쓰는 현재 확인한 결과, 구글크롬(Google Chrome), MS IE 11, MS 엣지(MS Edge)는 지원하였고 파이어폭스(FireFox)와 사파리(Safari)는 지원하지 않았다.

참고할만한 바깥고리

웹서버가 설정해서 전송하는 Content-Type 헤더

웹서버는 HTTP 응답에 Content-Type: 헤더를 선언하여 자신이 보내는 내용이 어떠한 용도로 사용될 수 있는 지를 지정한다. 이 헤더에는 MIME(Multipurpose Internet Mail Extensions) 형식이 사용된다. 영어 이름에서 알 수 있듯이 원래는 메일 전송시 사용하던 인코딩 방식이다.

웹서버는 일반적으로 HTML 전송시에는 "text/html"이라는 MIME 형식으로 보낸다. 그외의 경우에는 대개 접근하는 파일의 확장자에 의해서 MIME 형식이 결정된다. 예를 들어 JPG 이미지는 "image/jpeg", CSS 파일은 "text/css", MPG 파일은 "video/mpeg" 등과 같다. 확장자가 기존 MIME 형식에 정의되어 있지 않을 경우에는 텍스트 문서이면 "text/plain"이 이진 파일이면 "application/octet-stream"을 사용한다.

root@kali:~# curl -I http://www.daum.net/
HTTP/1.1 200 OK
X-UA-Compatible: IE=10
Expires: Sat, 01, Jan 1970 22:00:00 GMT
Pragma: no-cache
Cache-Control: no-cache, no-store, must-revalidate
P3P: CP="ALL DSP COR MON LAW IVDi HIS IVAi DELi SAMi OUR LEG PHY UNI ONL DEM STA INT NAV PUR FIN OTC GOV"
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
X-UA-Device-Type: pc
Date: Sun, 20 Nov 2016 15:55:40 GMT
Connection: keep-alive

root@kali:~#

위의 예는 일반적인 웹 접속시 볼 수 있는 "Content-Type: text/html" 헤더이다. 다음(Daum) 웹서버는 "charset=UTF-8"을 덧붙여서 문자 인코딩 방식이 UTF-8이라는 것을 함께 지정하고 있다.

root@kali:~# curl -v http://search.daum.net/OpenSearch.xml
*   Trying 180.70.134.60...
* Connected to search.daum.net (180.70.134.60) port 80 (#0)
> GET /OpenSearch.xml HTTP/1.1
> Host: search.daum.net
> User-Agent: curl/7.50.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Sun, 20 Nov 2016 13:34:28 GMT
< Server: Apache
< Last-Modified: Wed, 16 Nov 2016 07:25:54 GMT
< Accept-Ranges: bytes
< Content-Length: 716
< Vary: Accept-Encoding
< Connection: close
< Content-Type: application/xml
< 
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:referrer="http://a9.com/-/opensearch/extensions/referrer/1.0/">
    <ShortName>Daum</ShortName>
    <Description>Kakao Corp provider</Description>
    <Url template="http://search.daum.net/search?w=tot&DA=JU1&ie={inputEncoding}&src={referrer:source?}&q={searchTerms}" type="text/html"/>
    <Url template="http://suggest.search.daum.net/sushi/opensearch/pc?q={searchTerms}&DA=JU2" type="application/x-suggestions+json"/>
    <InputEncoding>UTF-8</InputEncoding>
    <Image height="16" type="image/x-icon" width="16">http://search.daum.net/favicon.ico</Image>
* Closing connection 0
</OpenSearchDescription>l;root@kali:~#

위의 사례에서 .xml 확장자 파일을 접근했을 때는 웹서버가 "application/xml"라는 MIME 형식을 전송하였다.

root@kali:~# curl -I http://lithium.daum.net/resources/external/top/trendad.min.js
HTTP/1.1 200 OK
Date: Sun, 20 Nov 2016 13:43:15 GMT
Server: Apache
Accept-Ranges: bytes
ETag: W/"664-1476342908000"
Last-Modified: Thu, 13 Oct 2016 07:15:08 GMT
Content-Length: 664
Connection: close
Content-Type: application/javascript

root@kali:~#

위의 사례에서 .js 확장자 파일을 접근했을 때는 웹서버가 "application/javascript"라는 MIME 형식을 전송하였다. 일반적으로는 "text/javascript"로 보내는 경우가 더 많다.

root@kali:~# curl -I http://img.naver.net/static/www/u/2013/0731/nmms_224940510.gif
HTTP/1.1 200 OK
Server: Testa/4.8.6
Last-Modified: Wed, 31 Jul 2013 13:49:47 GMT
Accept-Ranges: bytes
Content-Length: 4368
Access-Control-Allow-Origin: *
Content-Type: image/gif
Cache-Control: max-age=26414865
Expires: Fri, 22 Sep 2017 07:32:42 GMT
Date: Sun, 20 Nov 2016 14:04:57 GMT
Connection: keep-alive

root@kali:~#

.gif 확장자를 가지는 파일은 웹서버가 "image/gif"라는 마임형식으로 응답한다.

MIME 형식의 엄격한 적용을 통한 보안위협 완화

웹서버가 보내는 MIME 형식을 이용하여 일부 보안 수준을 높이려는 시도가 바로 X-Content-Type-Options: sniff 헤더이다. 이 HTTP 헤더가 선언되면 - 지원하는 웹 브라우저의 경우에는 - 지정된 MIME형식 이외의 다른 용도로 사용하고자 하는 것을 차단한다.

예를 들어보자. 어떤 공격자가 .jpg 확장자의 파일을 웹 서버에 업로드했다고 하자 - 실제로 이 파일은 JPEG 이미지가 아니라 내용은 자바스크립트르 담고 있다. 이 파일을 웹브라우저로 접근하면 웹서버는 확장자가 jpg이므로 image/jpeg라는 MIME 타입을 반환한다. 공격자가 이 이미지 파일을 자바스크립트로 사용하려고 할 때 웹서버가 X-Content-Type-Options: sniff 헤더를 함께 전송하면 웹브라우저는 이 파일의 MIME 형식이 text/javascriptapplication/javascript가 아니므로 자바스크립트 실행을 차단한다.

CSS에서도 JavaScript를 실행할 수 있으므로 사용자가 업로드한 파일이 .css 확장자가 아니면 CSS로 적용하는 것도 차단한다 - 따라서 첨부파일 업로드에서 업로드를 허용할 확장자 목록에 css가 포함되면 안된다.

X-Content-Type-Options 헤더 시험

웹핵누리집의 파일 다운로드 기능에 X-Content-Type-Options: nosniff 헤더를 켜고 끌 수 있도록 하였다. GET 변수 nosniff=off이면 X-Content-Type-Options: nosniff 헤더를 전송하지 않는다. 시험에 사용할 예제 파일인 20161120-fake-jpg-real-js.jpg 파일은 다음과 같은 내용을 담고 있다. 확장자만 jpg이고 실제 내용은 자바스크립트이다.

document.write('X-Content-Type-Options: nosniff 헤더가 선언되지 않으면 첨부파일을 자바스크립트로 실행할 수 있습니다.');

curl 명령어를 사용하여 X-Content-Type-Options: nosniff 헤더를 활성화 시켰을 때와 비활성화 시켰을 때의 통신 내용을 살펴보자.

root@kali:~# curl -v "http://webhack.dynu.net/?down=20161120-fake-jpg-real-js.jpg&nosniff=on"
*   Trying 122.32.19.138...
* Connected to webhack.dynu.net (122.32.19.138) port 80 (#0)
> GET /?down=20161120-fake-jpg-real-js.jpg&nosniff=on HTTP/1.1
> Host: webhack.dynu.net
> User-Agent: curl/7.49.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Sun, 20 Nov 2016 15:14:48 GMT
< Server: Apache
< Set-Cookie: NormalCookie=Not-HttpOnly-Cookie
< Set-Cookie: HttpOnlyCookie=HttpOnly-Cookie; httponly
< X-Frame-Options: DENY
< X-XSS-Protection: 1
< X-Content-Type-Options: nosniff
< Content-Description: File Transfer
< Content-Disposition: attachment; filename="20161120-fake-jpg-real-js.jpg"
< Expires: 0
< Cache-Control: must-revalidate
< Pragma: public
< Content-Length: 150
< Content-Type: application/octet-stream
< 
document.write('X-Content-Type-Options: nosniff 헤더가 선언되지 않으면 첨부파일을 자바스크립트로 실행할 수 있습니다.');
* Connection #0 to host webhack.dynu.net left intact
root@kali:~#

위는 X-Content-Type-Options: nosniff 헤더를 활성화시켰을 때의 통신내용이다. 파일 다운로드에서 URL 매개변수에 nosniff=on를 추가하면 - 실제로는 일상시에 - 아파치 웹서버가 X-Content-Type-Options: nosniff 헤더를 전송한다.

root@kali:~# curl -v "http://webhack.dynu.net/?down=20161120-fake-jpg-real-js.jpg&nosniff=off"
*   Trying 122.32.19.138...
* Connected to webhack.dynu.net (122.32.19.138) port 80 (#0)
> GET /?down=20161120-fake-jpg-real-js.jpg&nosniff=off HTTP/1.1
> Host: webhack.dynu.net
> User-Agent: curl/7.49.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Sun, 20 Nov 2016 15:13:35 GMT
< Server: Apache
< Set-Cookie: NormalCookie=Not-HttpOnly-Cookie
< Set-Cookie: HttpOnlyCookie=HttpOnly-Cookie; httponly
< X-Frame-Options: DENY
< X-XSS-Protection: 1
< Content-Description: File Transfer
< Content-Disposition: attachment; filename="20161120-fake-jpg-real-js.jpg"
< Expires: 0
< Cache-Control: must-revalidate
< Pragma: public
< Content-Length: 150
< Content-Type: application/octet-stream
< 
document.write('X-Content-Type-Options: nosniff 헤더가 선언되지 않으면 첨부파일을 자바스크립트로 실행할 수 있습니다.');
* Connection #0 to host webhack.dynu.net left intact
root@kali:~#

위는 X-Content-Type-Options: nosniff 헤더를 비활성화시켰을 때의 통신내용이다. 파일 다운로드에서 URL 매개변수에 nosniff=off를 추가하면 웹서버가 X-Content-Type-Options: nosniff 헤더를 전송하지 않는다.

가짜 JPG 파일의 JavaScript 실행여부 검사

가짜로 만든 JPG 파일과 X-Content-Type-Options: nosniff 헤더 선언 여부에 따라 웹브라우저가 자바스크립트를 어떻게 처리하는 지 살펴보자. 현재 당신이 접속한 웹브라우저는 "claudebot"이다.

[X-Content-Type-Options 헤더가 없을 때]

  • HTML:
    &nbsp;<script type="text/javascript" src="/?down=20161120-fake-jpg-real-js.jpg&nosniff=off"></script>&nbsp;
  • 자바스크립트 실행 결과:
      

[X-Content-Type-Options 헤더가 있을 때]

  • HTML:
    &nbsp;<script type="text/javascript" src="/?down=20161120-fake-jpg-real-js.jpg&nosniff=on"></script>&nbsp;
  • 자바스크립트 실행 결과:
      

X-Content-Type-Options 헤더를 지원하는 웹 브라우저의 경우에는 자바스크립트 실행결과가 달라야 한다. 미설정시에는 자바스크립트가 "X-Content-Type-Options: nosniff 헤더가 선언되지 않으면 첨부파일을 자바스크립트로 실행할 수 있습니다."라는 문자열을 출력한다. 설정 시에는 이 문자열이 출력되지 않는다.

X-Content-Type-Options 헤더를 지원하지 않는 웹 브라우저의 경우에는 자바스크립트 실행결과가 동일하게 "X-Content-Type-Options: nosniff 헤더가 선언되지 않으면 첨부파일을 자바스크립트로 실행할 수 있습니다."라는 문자열을 출력한다.

현재 당신이 접속한 웹브라우저는 "claudebot"이다. Google Chrome, FireFox 50, MS IE 11, MS Edge는 X-Content-Type-Options 헤더를 해석하였고, FireFox 49, Safari, Midori는 이 헤더를 지원하지 않았다.

이미지 파일에 대한 X-Content-Type-Options 헤더가 미치는 영향

X-Content-Type-Options 헤더는 크로스사이트스크립트 실행을 방지하기 위한 목적으로 제안되었다. 자바스크립트를 실행할 수 있는 text/javascript, text/css 등의 MIME 형식에 대해 사용될 것으로 예상할 수 있다. 실제 이미지 파일을 application/octet-stream MIME 형식으로 보냈을 때 이미지가 표시되는 지 살펴보자.

해질무렵 광화문과 해태 동상 풍경
[ <img src="/?down=20161120-real-jpg.jpg&nosniff=off"> ]

해질무렵 광화문과 해태 동상 풍경
[ <img src="/?down=20161120-real-jpg.jpg&nosniff=on"> ]

아마도 어떤 웹 브라우저를 쓰더라도 두 경우에 대해서 모두 "광화문과 해태상" 이미지가 출력될 것이다. 이는 X-Content-Type-Options 헤더가 설정되어 있더라도 "application/octet-stream" 마임형식을 "image/*" 마임형식으로 전용해서 쓰는 것을 허용한다는 뜻이다. 좀더 정확하게는 - 아마도 - 크롬이나 파이어폭스 등의 웹 브라우저는 실제 사용하고자 하는 마임형식이 "*/javascript", "*/css"인 경우에만 X-Content-Type-Options 헤더를 검사할 겻으로 추정해볼 수 있다.

위에서 출력한 이미지의 HTTP 헤더를 보고자 할 경우에는 다음 명령어를 사용하면 확인할 수 있다.

curl -I "http://webhack.dynu.net/?down=20161120-real-jpg.jpg&nosniff=off"
curl -I "http://webhack.dynu.net/?down=20161120-real-jpg.jpg&nosniff=on"

X-Content-Type-Options 헤더 설정방법

[Apache 웹서버]

Apache 설정 파일에 다음 한줄을 추가하고 재기동한다.

Header set X-Content-Type-Options nosniff

[NginX 웹서버]

NginX 설정 파일에 다음 한줄을 추가하고 재기동한다.

add_header X-Content-Type-Options nosniff;

[Apache Tomcat WAS]

public class SampleResponseFilter implements Filter  {

  @Override
  public void destroy() { }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException
  {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;
    // Protection against Type 1 Reflected XSS attacks
    res.addHeader("X-XSS-Protection", "1; mode=block");
    // Disabling browsers to perform risky mime sniffing
    res.addHeader("X-Content-Type-Options", "nosniff");
    chain.doFilter(req,res);
  }

  @Override
  public void init(FilterConfig filterConfig) throws ServletException { }
}

[Apache Tomcat 8 WAS]

Tomcat 8에서는 web.xml 파일에 다음과 같이 추가한다.

<filter>
    <filter-name>HeaderSecurityFilter</filter-name>
    <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>HeaderSecurityFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

[처음 작성한 날: 2016.11.20]    [마지막으로 고친 날: 2016.11.21] 


< 이전 글 : webhack.dynu.net 문자배너 만들기 - toilet (2016.11.21)

> 다음 글 : X-XSS-Protection헤더 시험 페이지 (2016.11.19)


크리에이티브 커먼즈 라이선스 이 저작물은 크리에이티브 커먼즈 저작자표시 4.0 국제 라이선스에 따라 이용할 수 있습니다.
잘못된 내용, 오탈자 및 기타 문의사항은 j1n5uk{at}daum.net으로 연락주시기 바랍니다.
문서의 시작으로 컴퓨터 깨알지식 웹핵 누리집 대문

.. -- -- | - .. .... | ... / .. .../ ... {] . .. .. .. ..| ...... .../ .../ .. ...... ... ... ] .. [ .../ ..../ ......./ .. ./// ../ ... .. ... .. -- -- | - .. .... | ... / .. .../ ... {] . .. .. .. ..| ...... .../ .../ .. ./// ../ ... .. ... ...| ..../ ./ ... / ..| ....| ........ / ... / .... ...... ... ... ] .. [ .../ ..../ ......./ .....| ..../ ./ ... / ..| ....| ........ / ... / .... ...| ..../ ./ ... / ..| ....| ........ / ... / .... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .