Menu

PHP htmlspecialchars(): HTML 이스케이프와 XSS 방지

htmlspecialchars($text)는 HTML에서 의미가 있는 다섯 문자(& < > " ')를 엔티티로 바꿔, 사용자 입력이 마크업으로 실행되지 않고 텍스트로 보이게 합니다. ENT_QUOTES, 속성 이스케이프, 이중 인코딩, htmlentities와 strip_tags를 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

htmlspecialchars($text)는 &, <, >, ", '를 &amp;, &lt;, &gt;, &quot;, &#039;로 바꿉니다. 페이지에 출력하는 모든 사용자 입력에 이것을 호출하면 입력이 HTML로 읽히지 않고 텍스트로 보입니다.

이 블록은 같은 댓글을 원래 그대로 한 번, 이스케이프해서 한 번 출력합니다. 실행하고 Page 탭에서 두 줄을 비교한 다음, 폼에 <h1>big</h1>이나 <img src=x> 같은 HTML을 직접 입력하고 Show를 누르세요.

원래 그대로인 줄에서는 브라우저가 태그를 따릅니다. 굵은 단어는 굵게 보이고, <script>를 입력한 방문자는 다른 모든 독자의 브라우저에서 자신의 스크립트를 실행시킵니다. 이 공격을 크로스 사이트 스크립팅(XSS)이라고 합니다. 이스케이프한 줄에서는 같은 문자가 &lt;b&gt;로 도착하고 브라우저는 그것을 텍스트로 그립니다. PHP가 실제로 출력한 엔티티를 보려면 Output 탭으로 바꾸세요.

htmlspecialchars가 변환하는 것

다섯 문자뿐이고 그 밖에는 없습니다. 글자, 악센트, 이모지는 그대로 통과합니다.

&가 목록에 있는 이유는 모든 엔티티가 그것으로 시작하기 때문입니다. 그대로 두면 &lt;를 언급하는 댓글이 <로 표시됩니다.

텍스트뿐 아니라 속성도 이스케이프하기

속성 안의 사용자 입력도 똑같이 이스케이프가 필요합니다. 없으면 값 안의 따옴표가 속성을 닫고 입력의 나머지가 새 속성이 됩니다. 여기서는 "이름"이 style 속성을 몰래 넣습니다. 실행하고 두 상자를 보세요.

안전하지 않은 상자에서 브라우저는 value="Ada" 뒤에 새 style 속성을 보므로 상자가 빨갛게 되고 Ada만 보입니다. 공격자는 거기에 style 대신 onfocus="..."를 쓰고, 그 코드가 실행됩니다. 안전한 상자에서는 모든 "가 &quot;가 되었으므로 문자열 전체가 value 안에 머물고 입력한 그대로 보입니다.

PHP 8.1부터 기본 플래그는 ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401이므로 작은따옴표도 이스케이프되고 '...'로 쓴 속성도 안전합니다. 예전 코드는 흔히 ENT_QUOTES를 직접 넘기며, PHP 7 이하에서는 그것이 필수였습니다.

템플릿용 짧은 헬퍼

템플릿에서 htmlspecialchars($x, ENT_QUOTES, 'UTF-8')를 수십 번 쓰면 번잡하므로 대부분의 프로젝트는 한 글자짜리 함수로 감쌉니다. Twig와 Blade 같은 템플릿 엔진은 모든 {{ $var }}에 대해 같은 일을 자동으로 합니다.

?string 타입과 ?? ''가 중요합니다. PHP 8.1부터 htmlspecialchars()에 null을 넘기는 것은 폐지 예정이라, 소개글이 없는 사용자마다 PHP가 폐지 예정 알림을 출력합니다.

이중 인코딩과 htmlspecialchars_decode

값이 두 번 이스케이프되면 독자에게 엔티티가 보입니다. &가 처음에는 &amp;가 되고 두 번째에는 &amp;amp;가 되며, 브라우저는 그것을 &amp;로 보여 줍니다. 보통 값이 저장될 때 이스케이프되고 출력될 때 다시 이스케이프되었다는 뜻입니다. 기존 엔티티를 그대로 두려면 double_encode: false를 넘기고, 되돌리려면 htmlspecialchars_decode()를 쓰세요.

진짜 해결책은 원래 텍스트를 저장하고 출력할 때만 이스케이프하는 것입니다. double_encode: false는 가져온 피드처럼 직접 만들지 않은 엔티티가 이미 든 텍스트를 위한 것입니다.

htmlspecialchars, htmlentities, strip_tags 비교

이 셋은 자주 헷갈립니다. 블록은 같은 입력에 셋을 모두 실행하고, 각각에 대해 PHP가 출력한 것과 브라우저가 그것을 어떻게 만드는지 보여 줍니다.

  • htmlspecialchars()는 HTML의 다섯 문자를 이스케이프합니다. HTML에 출력하는 모든 텍스트에 쓰세요.
  • htmlentities()는 é도 &eacute;로 바꿉니다. 페이지가 UTF-8이 아니던 시절에는 유용했지만, 지금은 소스를 읽기 어렵게 만들 뿐입니다.
  • strip_tags()는 태그를 지우고 텍스트는 남깁니다. HTML을 일반 텍스트로 바꾸는 용도(이메일 미리보기, 메타 설명)이지 보안용이 아닙니다. 마지막 행은 허용된 <b>가 onclick을 유지하고, 속성 안에 든 텍스트는 전혀 건드리지 않는다는 것을 보여 줍니다.

htmlspecialchars로 충분하지 않은 곳

htmlspecialchars()는 HTML 텍스트와 따옴표로 감싼 속성에 맞는 이스케이프입니다. 페이지의 다른 곳에는 다른 규칙이 있습니다.

  • URL에서는 http_build_query()나 urlencode()가 값을 인코딩하고, 그다음 htmlspecialchars()가 파라미터 사이의 &를 올바른 HTML로 만듭니다.
  • JavaScript에서는 json_encode()가 올바른 JS 값을 만들고, JSON_HEX_TAG가 <와 >를 \u003C와 \u003E로 바꾸므로 데이터 속의 </script>가 스크립트 태그를 닫을 수 없습니다.
  • 스킴을 확인하지 않고 사용자 입력을 href에 출력하지 마세요. htmlspecialchars('javascript:alert(1)')는 바뀌지 않고 클릭하면 여전히 실행됩니다. filter_var 페이지에서 보여 주듯 http와 https URL만 받아들이세요.

이 모든 것을 합친 폼 처리는 PHP 폼을 참고하세요.

자주 묻는 질문

PHP에서 htmlspecialchars는 무엇을 하나요?

&, <, >, ", '를 &amp;, &lt;, &gt;, &quot;, &#039;로 바꿉니다. 그러면 브라우저가 그 문자를 HTML로 읽지 않고 표시하므로, 폼에 입력한 <script>는 텍스트로 보이고 절대 실행되지 않습니다.

htmlspecialchars와 htmlentities의 차이는 무엇인가요?

htmlspecialchars()는 HTML에서 특별한 다섯 문자만 변환합니다. htmlentities()는 이름 있는 엔티티가 있는 모든 문자도 변환하므로 café가 caf&eacute;가 됩니다. UTF-8 페이지에서는 둘 다 똑같이 안전하고, htmlspecialchars()가 출력을 읽기 좋게 유지하므로 보통 이것을 고릅니다.

PHP 8에서도 ENT_QUOTES가 필요한가요?

안전을 위해서는 아닙니다. PHP 8.1부터 기본 플래그가 ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401이므로 작은따옴표도 이스케이프됩니다. 많은 코드베이스는 예전 버전에서도 같은 동작을 하고 읽는 사람에게 분명하도록 여전히 ENT_QUOTES, 'UTF-8'을 명시적으로 넘깁니다.

htmlspecialchars는 입력에 써야 하나요, 출력에 써야 하나요?

출력에 쓰세요. 원래 값을 저장하고 검증하며, HTML에 출력하는 순간 이스케이프하세요. 입력에서 이스케이프하면 데이터베이스에 &lt;가 저장되고, 길이와 검색이 깨지며, &amp;lt; 같은 이중 이스케이프로 이어집니다.

strip_tags로 XSS를 막을 수 있나요?

아니요. strip_tags()는 태그를 제거하지만 허용 태그 매개변수는 그 속성을 유지하므로 <b onclick="...">가 살아남고, 속성 안에 들어간 텍스트에는 아무 일도 하지 않습니다. 사용자 입력을 출력할 때는 htmlspecialchars()를 쓰세요.

Coddy 프로그래밍 언어 일러스트

Coddy로 코딩 배우기

시작하기