웹 취약점 점검에서 "서버 버전 정보 노출" 이 지적됐다. 응답 헤더에 다음과 같이 제품과 버전이 그대로 찍힌다.
Server: gunicorn/20.1.0
Server: Apache/2.4.37 (Red Hat Enterprise Linux)
Server: nginx/1.20.1
X-Powered-By: Express
버전이 드러나면 공격자가 그 버전에 해당하는 알려진 취약점을 바로 골라 시도할 수 있다. 정보 노출 자체가 침해로 이어지지는 않지만 정찰 비용을 낮춰 준다. 점검 항목에 늘 들어가는 이유다.
가리는 것은 완화책이지 해결책이 아니다. 버전을 숨기는 것보다 올리는 것이 먼저다. 두 가지를 같이 한다.
curl -sI https://app.example.com | grep -iE '^(server|x-powered-by|x-aspnet)'
curl -sI https://app.example.com -o /dev/null -D -
오류 응답에서만 노출되는 경우가 있으므로 404·500 도 함께 본다.
curl -sI https://app.example.com/nonexistent-path | grep -i '^server'
gunicorn 은 Server 헤더 값을 설정으로 바꿀 수 있다. 설정 파일에 넣는다.
# gunicorn.conf.py
bind = "0.0.0.0:8000"
workers = 4
# 기본은 gunicorn/<버전>. 빈 문자열이나 일반 문자열로 바꾼다
gunicorn 자체 설정만으로 완전히 없애기는 어렵다. 실무에서는 앞단 리버스 프록시에서 덮어쓰는 방식이 확실하고, 애플리케이션 서버를 직접 외부에 노출하지 않는다는 원칙과도 맞는다.
애플리케이션 계층에서 지우는 방법도 있다. WSGI 미들웨어로 응답 헤더를 손본다.
class StripServerHeader:
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
def _start(status, headers, exc_info=None):
headers = [(k, v) for k, v in headers
if k.lower() not in ("server", "x-powered-by")]
headers.append(("Server", "web"))
return start_response(status, headers, exc_info)
return self.app(environ, _start)
application = StripServerHeader(application)
버전 번호만 빼려면 다음 한 줄이면 된다. 제품 이름은 남는다.
http {
server_tokens off;
}
이름까지 바꾸려면 headers-more 모듈이 필요하다.
more_set_headers "Server: web";
more_clear_headers "X-Powered-By";
프록시 뒤 애플리케이션이 보낸 헤더도 여기서 덮어쓴다.
ServerTokens Prod
ServerSignature Off
ServerTokens Prod 로 두면 Server: Apache 까지만 나온다. 이름 자체를 바꾸려면 mod_security 의 SecServerSignature 를 쓴다.
백엔드가 보낸 헤더를 지우려면 다음을 쓴다.
Header unset X-Powered-By
Header always unset X-Powered-By
| 헤더 | 나오는 곳 | 끄는 법 |
|---|---|---|
X-Powered-By |
PHP, Express | PHP expose_php = Off, Express app.disable('x-powered-by') |
X-AspNet-Version |
ASP.NET | web.config 의 httpRuntime enableVersionHeader="false" |
X-Generator |
CMS | 플러그인·설정으로 제거 |
같은 점검에서 보통 함께 지적되므로 한 번에 정리한다.
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
HSTS 는 HTTPS 가 확실히 동작하는 것을 확인한 뒤에 켠다. 한번 브라우저에 각인되면 되돌리기 어렵다.
바꾼 뒤 다시 받아 본다. 정상 응답과 오류 응답 모두 확인한다.
curl -sI https://app.example.com | grep -iE 'server|x-powered'
curl -sI https://app.example.com/does-not-exist | grep -i server
Server 헤더를 지워도 오류 페이지 본문, 디렉터리 목록, 기본 404 화면에 제품 이름이 남는 경우가 많다. 사용자 정의 오류 페이지를 함께 둔다.
TLS 핸드셰이크의 암호군 조합이나 응답 특성으로도 제품을 추정할 수 있다. 헤더 제거는 손쉬운 단서를 없애는 정도의 의미다.