htmlspecialchars($text) ממירה את &, <, >, " ו-' ל-&, <, >, " ו-'. קראו לה על כל פיסת קלט משתמש שאתם מדפיסים לתוך דף, והקלט יוצג כטקסט במקום להיקרא כ-HTML.
הבלוק מדפיס את אותה תגובה פעמיים, פעם גולמית ופעם אחרי escape. הריצו אותו והשוו את שתי השורות בלשונית Page, ואז הקלידו HTML משלכם בטופס, למשל <h1>big</h1> או <img src=x>, ולחצו על Show.
בשורה הגולמית הדפדפן מציית לתגיות: המילה המודגשת מודגשת, ומבקר שמקליד <script> גורם לסקריפט שלו לרוץ בדפדפן של כל קורא אחר. המתקפה הזו נקראת cross-site scripting (XSS). בשורה שעברה escape אותם תווים מגיעים כ-<b> והדפדפן מצייר אותם כטקסט. עברו ללשונית Output כדי לראות את הישויות ש-PHP הדפיסה בפועל.
מה htmlspecialchars ממירה
חמישה תווים, שום דבר אחר. אותיות, סימנים דיאקריטיים ואימוג'י עוברים בלי שינוי.
& נמצא ברשימה כי הוא מתחיל כל ישות: אם הוא היה נשאר כמו שהוא, תגובה שמזכירה < הייתה מוצגת כ-<.
escape למאפיינים, לא רק לטקסט
קלט משתמש בתוך מאפיין צריך escape באותה מידה. בלעדיו, מירכאה בערך סוגרת את המאפיין ושאר הקלט הופך למאפיינים חדשים. כאן ה"שם" מגניב פנימה מאפיין style; הריצו והסתכלו על שתי התיבות.
בתיבה הלא בטוחה הדפדפן רואה value="Ada" ואחריו מאפיין style חדש, ולכן התיבה הופכת לאדומה ומציגה רק Ada. תוקף היה כותב שם onfocus="..." במקום style, והקוד שלו היה רץ. בתיבה הבטוחה כל " הפכה ל-", ולכן כל המחרוזת נשארת בתוך value ומוצגת כפי שהוקלדה.
מאז PHP 8.1 הדגלים של ברירת המחדל הם ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, ולכן גם גרשיים בודדים עוברים escape ומאפיינים שנכתבו עם '...' בטוחים. קוד ישן מעביר לעיתים קרובות ENT_QUOTES ידנית, וב-PHP 7 ומטה זה היה הכרחי:
פונקציית עזר קצרה לתבניות
כתיבה של htmlspecialchars($x, ENT_QUOTES, 'UTF-8') עשרות פעמים בתבנית רועשת, ולכן רוב הפרויקטים עוטפים אותה בפונקציה בת אות אחת. מנועי תבניות כמו Twig ו-Blade עושים את אותו הדבר אוטומטית לכל {{ $var }}.
הטיפוס ?string וה-?? '' חשובים: העברה של null ל-htmlspecialchars() הוצאה משימוש מאז PHP 8.1, וה-PHP שלכם הייתה מדפיסה הודעת deprecation לכל משתמש בלי bio.
קידוד כפול ו-htmlspecialchars_decode
אם ערך עובר escape פעמיים, הקורא רואה את הישויות: & הופך ל-& בפעם הראשונה ול-&amp; בשנייה, שהדפדפן מציג כ-&. זה אומר בדרך כלל שהערך עבר escape כשנשמר ושוב כשהודפס. העבירו double_encode: false כדי להשאיר ישויות קיימות כמו שהן, והשתמשו ב-htmlspecialchars_decode() כדי לחזור אחורה.
התיקון האמיתי הוא לשמור טקסט גולמי ולבצע escape רק בפלט. double_encode: false מיועד לטקסט שכבר מכיל ישויות שלא אתם יצרתם, כמו פיד שיובא.
htmlspecialchars מול htmlentities מול strip_tags
שלוש הפונקציות האלה מתבלבלות לעיתים קרובות. הבלוק מריץ את כולן על אותו קלט ומראה, לכל אחת, מה PHP מדפיסה ומה הדפדפן עושה מזה:
htmlspecialchars()מבצעת escape לחמשת תווי ה-HTML. השתמשו בה לכל טקסט שאתם מדפיסים לתוך HTML.htmlentities()הופכת גם אתéל-é. היא הייתה שימושית כשדפים לא היו ב-UTF-8; היום היא רק הופכת את קוד המקור לקשה יותר לקריאה.strip_tags()מוחקת תגיות ושומרת על הטקסט שלהן. היא נועדה להפוך HTML לטקסט פשוט (תצוגה מקדימה של אימייל, meta description), לא לאבטחה: השורה האחרונה מראה ש-<b>מותר שומר על ה-onclickשלו, וטקסט שמוצב בתוך מאפיין לא נוגעים בו בכלל.
איפה htmlspecialchars לא מספיקה
htmlspecialchars() היא ה-escape הנכון לטקסט HTML ולמאפיינים במירכאות. למקומות אחרים בדף יש כללים אחרים:
- בכתובת URL,
http_build_query()אוurlencode()מקודדות את הערך;htmlspecialchars()הופכת אז את ה-&שבין הפרמטרים ל-HTML תקין. - ב-JavaScript,
json_encode()מפיקה ערך JS תקין, ו-JSON_HEX_TAGהופך את<ואת>ל-\u003Cול-\u003E, כך ש-</script>בתוך הנתונים לא יכול לסגור את תגית הסקריפט. - לעולם אל תדפיסו קלט משתמש לתוך
hrefבלי לבדוק את הסכמה:htmlspecialchars('javascript:alert(1)')לא משתנה ועדיין רץ כשלוחצים עליו. קבלו רק כתובותhttpו-https, כמו שמוצג בעמוד filter_var.
לטיפול בטפסים שמחבר את כל זה יחד, ראו טפסים ב-PHP.
שאלות נפוצות
מה htmlspecialchars עושה ב-PHP?
היא מחליפה את &, <, >, " ו-' ב-&, <, >, " ו-'. הדפדפן מציג אז את התווים האלה במקום לקרוא אותם כ-HTML, ולכן <script> שהוקלד בטופס מוצג כטקסט ואף פעם לא רץ.
מה ההבדל בין htmlspecialchars ל-htmlentities?
htmlspecialchars() ממירה רק את חמשת התווים המיוחדים ב-HTML. htmlentities() ממירה גם כל תו שיש לו ישות עם שם, ולכן café הופך ל-café. בדפי UTF-8 שתיהן בטוחות באותה מידה, ו-htmlspecialchars() שומרת על הפלט קריא, ולכן היא הבחירה הרגילה.
האם עדיין צריך ENT_QUOTES ב-PHP 8?
לא בשביל בטיחות: מאז PHP 8.1 הדגלים של ברירת המחדל הם ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, ולכן גם גרשיים בודדים עוברים escape. בסיסי קוד רבים עדיין מעבירים ENT_QUOTES, 'UTF-8' במפורש כדי שהקריאה תתנהג אותו דבר בגרסאות ישנות ותהיה ברורה לקוראים.
להשתמש ב-htmlspecialchars בקלט או בפלט?
בפלט. שמרו ואמתו את הערך הגולמי, ובצעו לו escape ברגע שאתם מדפיסים אותו לתוך HTML. escape בקלט שומר < במסד הנתונים שלכם, שובר אורכים וחיפושים, ומוביל ל-escape כפול כמו &lt;.
האם strip_tags מספיקה כדי למנוע XSS?
לא. strip_tags() מסירה תגיות, אבל פרמטר התגיות המותרות שלה שומר על המאפיינים שלהן, ולכן <b onclick="..."> שורד, והיא לא עושה כלום לטקסט שמוצב בתוך מאפיין. השתמשו ב-htmlspecialchars() כשמדפיסים קלט משתמש.