§

ورودی

حالت
سبک موجودیت
محدوده رمزگذاری
§

خروجی

توسعه‌دهندگان ایرانی در پروژه‌های پورتال‌های دولتی .ir، سیستم‌های مدیریت محتوا خبرگزاری‌هایی مثل ایرنا و ایسنا، و فروشگاه‌های اینترنتی هر بار که متن کاربر وارد صفحه سرور-رندرشده می‌شود به رمزگذاری موجودیت HTML نیاز دارند. راهنمای OWASP برای جلوگیری از XSS رمزگذاری موجودیت را قانون شماره ۱ برای بافت بدنه HTML می‌داند. این کدک درون‌مرورگری از حدود ۲۰۰ موجودیت نامی رایج و بازگشت عددی برای بقیه استفاده می‌کند، بنابراین نتیجه با آنچه `htmlspecialchars` یا auto-escape ری‌اکت در محیط production تولید می‌کنند مطابقت دارد.

نحوه عملکرد رمزگذاری موجودیت HTML

یک موجودیت HTML یک مرجع کاراکتری است که مرورگر آن را به یک کاراکتر واحد تبدیل می‌کند. پنج کاراکتر رزروشده HTML (<، >، &، "، ') همیشه هنگام رندر متن به‌عنوان HTML نیاز به رمزگذاری دارند؛ هر چیز دیگری اختیاری است و به رمزگذاری سند بستگی دارد.

  1. انتخاب حالت و محدوده. حالت رمزگذاری ورودی را کاراکتر به کاراکتر پردازش می‌کند. حالت رمزگشایی به دنبال الگوهای موجودیت می‌گردد. تغییر محدوده تعیین می‌کند آیا فقط پنج کاراکتر ایمن HTML رمزگذاری شوند، یا هر نقطه کد غیر ASCII نیز بازنویسی شود.
  2. انتخاب سبک موجودیت. موجودیت‌های نامی (&copy;) در سورس خوانا هستند. مراجع اعشاری (&#169;) و hex (&#xA9;) هر نقطه کد یونیکد را بدون نیاز به نام منتقل می‌کنند. کلاینت‌های ایمیل قدیمی‌تر و پارسرهای XML فرم‌های عددی را ترجیح می‌دهند.
  3. پردازش ورودی. در رمزگذاری، هر نقطه کد را می‌خوانیم و آن را در جدول داخلی حدود ۲۰۰ موجودیت نامی رایج جستجو می‌کنیم. عدم تطابق به عددی برمی‌گردد. در رمزگشایی، با یک regex واحد اسکن می‌کنیم که &name;، &#NNN; و &#xHH; را در یک مرحله تطبیق می‌دهد.
  4. نگاشت به کاراکترها. تطابق‌های نامی از طریق جدول معکوس حل می‌شوند. تطابق‌های عددی از String.fromCodePoint با پایه ۱۰ یا ۱۶ عبور می‌کنند. موجودیت‌های نامی ناشناخته دست‌نخورده باقی می‌مانند تا ورودی جزئی بدون از دست دادن داده برگردد.
  5. حالت زنده. حالت زنده را فعال کنید و هر ضربه کلید با تأخیر ۱۵۰ میلی‌ثانیه تبدیل را مجدداً اجرا می‌کند. هنگامی که در حال تنظیم یک قطعه کد هستید و قبل از الصاق در یک قالب بازخورد فوری می‌خواهید مفید است.

چرا موجودیت‌های HTML را رمزگذاری کنیم

  • جلوگیری از شکستن صفحه توسط ورودی کاربر. وقتی کاربر یک < تصادفی در یک کادر کامنت تایپ می‌کند، انداختن آن متن مستقیم در HTML بقیه صفحه را بازنویسی می‌کند. رمزگذاری کاراکترهای رزروشده ابتدا یعنی مرورگر کاراکتر را رندر می‌کند به جای اینکه آن را به‌عنوان شروع یک تگ تفسیر کند.
  • معتبر نگه داشتن مقادیر attribute. جاسازی یک رشته نقل‌قولی در یک attribute HTML نیاز دارد که نقل‌قول داخلی با &quot; (برای attr های دوتا-نقل‌قول) یا &#39; (برای تک-نقل‌قول) جایگزین شود. در غیر این صورت پارسر attribute را زودهنگام می‌بندد و بقیه خط به markup سرگردان تبدیل می‌شود.
  • خنثی‌سازی HTML تصادفی در داده‌های ذخیره‌شده. لاگ‌ها، گزارش‌های باگ و صادرات چت اغلب شامل براکت‌های زاویه‌ای و علامت & واقعی هستند. رمزگذاری موجودیت dump پیش از الصاق در یک صفحه مستندات آن محتوا را به‌عنوان متن قابل مشاهده نگه می‌دارد به جای اینکه رندرر یا آشکارساز پیوند خودکار را فعال کند.
  • اشتراک‌گذاری امن قطعه کد. ارسال یک تگ نمونه مثل <script>alert(1)</script> در یک پست وبلاگ، ایمیل یا پیام Slack نیاز دارد که براکت‌ها رمزگذاری شوند تا قطعه کد نمایش داده شود به جای اینکه اجرا شود. همین تکنیک بدنه‌های فید RSS و فیلدهای `description` در JSON-LD را پوشش می‌دهد.

کاربردهای رایج

رمزگذاری موجودیت هر جا که متن خام در زمان اجرا به HTML ترکیب می‌شود ظاهر می‌شود — حتی زمانی که فریم‌ورک معمولاً آن را برای شما مدیریت می‌کند، ابزار دستی برای لحظاتی که این کار را انجام نمی‌دهد مفید است.

  • قالب‌های سرور-رندرشده: Jinja2، ERB، Twig و Handlebars به‌طور پیش‌فرض escape خودکار انجام می‌دهند، اما بلوک‌های raw و نشانگرهای `safe` آن را خاموش می‌کنند — کدک به شما امکان می‌دهد تأیید کنید escape چه چیزی تولید می‌کرد.
  • نوشتن ایمیل و خبرنامه: بسیاری از موتورهای قالب‌بندی ESP فیلدهای merge را escape خودکار نمی‌کنند، بنابراین نقل‌قول‌های هوشمند و علائم copyright در نام‌های ارائه‌شده توسط کاربر نیاز به پیش-رمزگذاری دارند.
  • مستندات و نمونه‌های کد: الصاق یک تگ HTML نمونه در یک پست وبلاگ Markdown یا یک قطعه کد سایت استاتیک نیاز دارد که براکت‌ها رمزگذاری شوند تا رندرر آن را به‌عنوان متن قابل مشاهده تلقی کند.

یک مثال عملی

<script>alert('hi')</script> را با حالت تنظیم‌شده روی رمزگذاری، سبک نامی، محدوده حداقلی در ورودی الصاق کنید. خروجی می‌خواند &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;. سبک را به Numeric hex تغییر دهید و همان ورودی تولید می‌کند &#x3C;script&#x3E;alert(&#x27;hi&#x27;)&#x3C;/script&#x3E;. حالت را به رمزگشایی تبدیل کنید، رشته رمزگذاری‌شده را دوباره الصاق کنید و تگ اصلی سالم بازمی‌گردد.

FAQ

موجودیت‌های HTML چیستند؟

موجودیت‌های HTML مراجع کاراکتری هستند که مرورگر هنگام تجزیه صفحه آن‌ها را به کاراکترهای واحد تبدیل می‌کند. در سه فرم هستند: نامی (مثل &amp; برای &)، عددی اعشاری (&#38;) و عددی hex (&#x26;). پنج کاراکتر رزروشده HTML (<، >، &، "، ') هر بار که متن در HTML رها می‌شود نیاز به رمزگذاری دارند. حدود ۲۲۲۵ موجودیت نامی دیگر نمادها، لهجه‌ها و حروف یونانی را پوشش می‌دهند اما وقتی رمزگذاری سند UTF-8 باشد اختیاری هستند.

چه زمانی باید از موجودیت نامی در مقابل عددی استفاده کنم؟

از موجودیت‌های نامی استفاده کنید وقتی می‌خواهید سورس واضح خوانده شود (یک انسانی که &copy; را در یک قالب می‌بیند بلافاصله آن را می‌فهمد). از عددی (اعشاری یا hex) استفاده کنید وقتی مصرف‌کننده قدیمی‌تر یا سخت‌گیرتر است — پارسرهای XML، کلاینت‌های ایمیل قدیمی و برخی خواننده‌های فید فقط زیرمجموعه کوچکی از موجودیت‌های نامی HTML5 را می‌شناسند، و همه آن‌ها فرم‌های عددی را تشخیص می‌دهند. hex معمولاً در زمینه‌های امنیت‌محور برنده می‌شود چون با نماد نقطه کد یونیکد در اسناد spec مطابقت دارد.

آیا رمزگشایی موجودیت‌های hex مثل & را پشتیبانی می‌کند؟

بله. رمزگشا از یک regex واحد استفاده می‌کند که هر سه فرم موجودیت را در یک مرحله تطبیق می‌دهد: &name;، &#NNN; و &#xHH;. تطابق‌های عددی با String.fromCodePoint با پایه ۱۰ یا ۱۶ حل می‌شوند. ورودی مختلط (نامی و عددی در یک رشته) به‌درستی رمزگشایی می‌شود و نام‌های ناشناخته به‌عنوان متن تحت‌اللفظی باقی می‌مانند.

آیا این برای استفاده با ورودی غیرمعتمد ایمن است؟

خود کدک فقط در مرورگر است و ورودی شما را هیچ جا نمی‌فرستد. اینکه آیا خروجی برای جاسازی ایمن است به زمینه بستگی دارد. رمزگذاری موجودیت زمینه‌های بدنه HTML و مقدار attribute را پوشش می‌دهد که مورد OWASP قانون شماره ۱ را شامل می‌شود. زمینه‌های JavaScript (کنترل‌کننده‌های رویداد inline، بلوک‌های `<script>`)، زمینه‌های CSS و URL هر کدام قوانین رمزگذاری خاص خود را نیاز دارند — رمزگذاری موجودیت به‌تنهایی در آنجا کافی نیست. برای دفاع عمیق سمت سرور، این را با یک موتور قالب‌بندی context-aware مثل DOMPurify یا escape خودکار فریم‌ورک‌تان ترکیب کنید.

رمزگذاری موجودیت سمت مرورگر در مرز بین ورودی کاربر و HTML رندرشده قرار دارد. انجام محلی تبدیل یعنی می‌توانید بررسی کنید فریم‌ورک‌تان چه چیزی تولید می‌کرد، بدون اینکه متن اصلی را به یک ابزار شخص ثالث ارسال کنید.