웹서버 Nginx를 이용해 HTTP 보안 헤더 적용 시 주의점에 대해서 알아봅니다. 그 동안 일반 인터넷에 널려 있는 방법으로 보안 헤더를 적용했는데 알고 보니 전혀 작동하지 않았더군요.
이는 최근 각광을 받고 있는 웹서버인 Nginx에서 볼 수 있는 주의점인데요. 한번 그 내용을 살펴보도록 하죠.
[쇼핑몰 보안] NGINX에서 HTTP 보안 헤더(Security Headers) 적용 時 주의점
쇼핑몰 구축기를 연재하는 이유
최근 지인이 워드프레스를 이용해 쇼핑몰 구축을 시도하면서 배웠던 배웠던 다양한 경험들을 해당 쇼핑몰 블로그에 연재해 왔는데요.
쇼핑몰이 상품만 파는 것이 아니라 쇼핑몰을 방문하는 고객들에게 열가지 유용한 정보를 제공하는 블로그의 효용성이 높다는 점을 십분 활용하고, 처음 시작하는 쇼핑몰의 신뢰성을 주기 위해 비록 삽질이지만 삽질기를 낱낱히 공개하기로 했다고 하네요.
그 쇼핑몰의 주소는 https://puripia.com로 아직도 공사중이기는 합니다.)
쇼핑몰 구축 시 도와주었던 인연으로 그 쇼핑몰을 알리고 쇼핑몰 구축 경험담을 보다 널리 알리기 위해서 여기 happist.com에도 같이 공유합니다. 조금 사심이 있기는 합니다.
[쇼핑몰 보안] NGINX에서 HTTP 보안 헤더(Security Headers) 적용 時 주의점
쇼핑몰 보안을 살피다보면 참으로 여러가지를고민해야 한다는 것을 알 수 있습니다.
이전에 SSL인증서 설정을 가장 효과적으로 할 수 있는 방안에 대해 “SSL 보안 등급 A+에 도전하는 Let’s Encrypt 인증서 세팅 방법”라는 글에서 살펴 본적이 있는데요.
위 글에서는 SSL 인증서 관련 내용뿐만이 아니라 외에도 쇼핑몰이나 인터넷 문서의 헤더 부분의 보안을 강화하기 위한 HTTP Headers 보안 방안에 대해서 소개했었죠.
그런데 이 HTTP Headers 보안 옵션이 제대로 적용되려면 피해야할 주의점이 있습니다.
1. 적용했던 HTTP Sucrity Headers 옵션
우선 그동안 제가 적용했던 그리고 이번 쇼핑몰을 구상하면서 정리했던 최종 HTTP Headers 보안 옵션입니다.
각 옵션에 대한 설명은 인터넷에 아주 자세하게 널려있고, “SSL 보안 등급 A+에 도전하는 Let’s Encrypt 인증서 세팅 방법”라는 글에서도 어느 정도 설명해 놓았기 때문에 넘어가겠습니다.
# Security headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Content-Security-Policy "default-src 'self' www.google-analytics.com ajax.googleapis.com www.google.com google.com gstatic.com www.gstatic.com connect.facebook.net facebook.com;";
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "origin";
# HTTP Public Key Pinning Extension HPKP for NGINX, https://raymii.org/s/articles/HTTP_Public_Key_Pinning_Extension_HPKP.html
add_header Public-Key-Pins 'pin-sha256="klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY="; pin-sha256="633lt352PKRXbOwf4xSEa1M517scpD3l5f79xMD9r9Q="; max-age=2592000; includeSubDomains';
add_header Feature-Policy "geolocation none;midi none;notifications none;push none;sync-xhr none;microphone none;camera none;magnetometer none;gyroscope none;speaker self;vibrate none;fullscreen self;payment none;";
Code language: PHP (php)
2. 적용되지 않는 HTTP Sucrity Headers 옵션
적용하고 있는 웹서버인 nginx 문법에 맞추어 HTTP Header의 보안 옵션을 적용했지만 막상 테스트해보면 적용되지 않았다고 나옵니다.
뭐 이를 테스트하는 방법은 아래처럼 여러가지 방법이 있습니다.
- 서버에서 curl -I https://MyDomain.com이라는 명령 사용
- Mozilla Observatory 테스트
- Security Headers Sponsored by netsparker에서 테스트
- SSL Decoder 에서 SSL 세팅과 일부 HTTP Headers보안에 대해 점검해 볼 수 있음
아래는 Mozilla Observatory에서 측정했는데 아무것도 적용디지 않았다고 나오는 황당한 상황을 캡춰한 것입니다.
3. 왜 HTTP Sucrity Headers 옵션은 적용되지 않을까?
HTTP 보안 헤더 적용 가이드에서 이야기한대로 충실하게 관련 옵션을 적용했는데요. .
그럼에도 불구하고 위에서 공유한 이미지처럼 실제로는 적용이 안되고 있습니다.
모든 테스트 결과에서 공통으로 적용되지 않았다고 나오기 때문에 의심의 여지가 없었습니다.
이 사이트뿐만이 아니라 비슷하게 적용하 다른 사이트도 다 적용이 안되더군요.
오랬동안 왜 그렇까하고 의구심만 품고 제대로 해결하지 못했는데요.
이번 쇼핑몰을 개발하면서, 쇼핑몰은 보안이 어떤 사이트보다도 중요하기 때문에 제대로 적용하자고 상당히 많은 고민과 검색 등을 통해서 해결 방안을 찾았습니다.
그리고 그 원인을 찾을 수 있었습니다. 이에 대한 실마리는 “Be very careful with your add_header in Nginx! You might make your site insecure”라는 글에서 찾을 수 있었습니다.
그것은 nginx 설정하는 conf 파일의 여러군데에서 add_header라는 명령이 나오면 앞에서 설정한 모든 add_header옵션은 무효화된다는 것입니다.
저는 그동안 서버 설정 문서에서 다양한 곳에서 add_header명령을 사용했던 것입니다. 그리고 맨 마디만에 사용한 add_header옵션은 거의 의미가 없는 옵션이었기 때문에 메인 옵션이 적용되지 않았던 것입니다.
아래 add_header가 남발된 몇몇 명령을 보시죠.
# Add PHP handler
location ~ \.php$ {
try_files $uri =404; # comment out this line if php-fpm is hosted on a remote machine
include /etc/nginx/fastcgi.conf;
fastcgi_cache WORDPRESS;
add_header X-Cache $upstream_cache_status;
# Aggressive caching for static files that rarely/never change
location ~ \.(?:asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|eot|exe|gif|gz|gzip|ico|jpg|jpeg|jpe|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mpe|mpp|odb|odc|odf|odg|odp|ods|odt|ogg|ogv|otf|pdf|png|pot|pps|ppt|pptx|ra|ram|svg|svgz|swf|tar|t?gz|tif|tiff|ttf|wav|webm|wma|woff|wri|xla|xls|xlsx|xlt|xlw|zip)$ {
expires 31536000s;
#add_header Pragma public; # HTTP1.0에서 사용하던 명령으로 같이 사용하지 않음
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
location ~ \.(?:css|js)$ {
expires 86400s;
#add_header Pragma public; # HTTP1.0에서 사용하던 명령으로 같이 사용하지 않음
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
Code language: PHP (php)
4. 해결 방안
문제 원인을 알았기 때문에 HTTP 보안 헤더 옵션을 적용하고 그 후에 나오는 add_header 옵션은 전부 제거했습니다.
이래더니 제대로 작동합니다. 생각보다 간단한 문제였는데요. 그것을 모르고 좋다는 옵션을 무조건 가져다 사용하다보니 이런 역효과가 나왔다는 교훈을 얻었습니다.
만약 뒤에서 굳이 add_header 옵션을 사용해야 한다면 general-security-headers.conf와 같은 보안 헤더 적용 옵션을 정의한 세팅 파일을 만들고, 적용 시점에서 불러오는 방법읗 사용하라고 조언하고 있습니다.
그 코드는 아래처럼 구성됩니다.
먼저 /etc/nginx 폴더에 general-security-headers.conf 파일을 만듭니다.
# general-security-headers.conf 파일 만들기
nano /etc/nginx/general-security-headers.conf
# 아래처럼 보안 헤더 옵션 설정
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Content-Security-Policy "default-src 'self' www.google-analytics.com ajax.googleapis.com www.google.com google.com gstatic.com www.gstatic.com connect.facebook.net facebook.com;";
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "origin";
# HTTP Public Key Pinning Extension HPKP for NGINX, https://raymii.org/s/articles/HTTP_Public_Key_Pinning_Extension_HPKP.html
add_header Public-Key-Pins 'pin-sha256="klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY="; pin-sha256="633lt352PKRXbOwf4xSEa1M517scpD3l5f79xMD9r9Q="; max-age=2592000; includeSubDomains';
add_header Feature-Policy "geolocation none;midi none;notifications none;push none;sync-xhr none;microphone none;camera none;magnetometer none;gyroscope none;speaker self;vibrate none;fullscreen self;payment none;";
Code language: PHP (php)
다음에는 사이트 conf 파일을 편집해 아래와 같은 내용을 추가합니다.
# 미리 만들어 놓은 보안 헤더 설정 파일을 불러옵니다.
include /etc/nginx/general-security-headers.conf;
location / {
try_files $uri /index.html;
# 이 Location에서 독자적인 add_header 옵션을 가져야기 때문에 다시 불러옵니다.
include /etc/nginx/snippets/general-security-headers.conf;
}
Code language: PHP (php)
5. 적용 결과 Test
제대로 적용이 되었는지 테스트해보았는데요.
이는 보안 헤더 부분만 집중적으로 다루고 있는 curity Headers Sponsored by netsparker 에서 테스트해 보았습니다.