اشتراکها
مرورگرهای جدید تحت زیر مجموعهای به نام Content Security Policy، قابلیتهای توکاری را اضافه کردهاند تا حملاتی مانند XSS را حتی در برنامهی وبی که برای این نوع حملات تمهیداتی را درنظر نگرفتهاست، خنثی کنند. این قابلیتها به صورت پیش فرض فعال نبوده و نیاز است برنامه نویس صراحتا درخواست فعال شدن آنها را از طریق افزودن تعدادی هدر مشخص به Response، ارائه دهد. در ادامه این هدرها را بررسی خواهیم کرد.
غیرفعال کردن اجرای اسکریپتهای inline
عمدهی حملات XSS زمانی قابلیت اجرا پیدا میکنند که مهاجم بتواند به طریقی (ورودیهای اعتبارسنجی نشده)، اسکریپتی را به درون صفحهی جاری تزریق کند. بنابراین اگر ما به مرورگر اعلام کنیم که دیگر اسکریپتهای inline را پردازش نکن، سایت را تا حد زیادی در مقابل حملات XSS مقاوم کردهایم. این قابلیت به صورت پیش فرض خاموش است؛ چون به طور قطع فعال سازی آن بسیاری از سایتهایی را که عادت کردهاند اسکریپتهای خود را داخل صفحات وب مدفون کنند، از کار میاندازد. این نوع سایتها باید به روز شده و اسکریپتها را از طریق فایلهای خارجی js، به سایت و صفحات خود الحاق کنند.
برای فعال سازی این قابلیت، فقط کافی است هدرهای زیر به Response اضافه شوند:
سطر اول به زودی تبدیل به یک استاندارد W3 خواهد شد؛ اما فعلا فقط توسط کروم 25 به بعد پشتیبانی میشود. سطر دوم توسط مرورگرهایی که از موتور WebKit استفاده میکنند، پشتیبانی میشود و سطر سوم مخصوص فایرفاکس است و IE 10 به بعد.
بعد از فعال شدن این قابلیت، فقط اسکریپتهایی که از طریق دومین شما به صفحه الحاق شدهاند، قابلیت اجرا را خواهند یافت و کلیه اسکریپتهای مدفون شده داخل صفحات، دیگر اجرا نخواهد شد. در این حالت اگر از CDN برای الحاق اسکریپتی استفاده میکنید، مثلا مانند الحاق jQuery به صفحه، نیاز است مسیر آنرا صراحتا در این هدر ذکر کنید:
علاوه بر آن حتی میشود پردازش تمام منابع مورد استفاده را نیز مانند تصاویر، شیوهنامهها، فایلهای فلش و غیره، به دومین جاری محدود کرد:
بدیهی است پس از آشنایی با این مورد، احتمالا در پروژههای جدید خود از آن استفاده کنید (چون inline scriptهای فعلی شما را کاملا از کار میاندازد).
نحوهی اضافه کردن هدرهای Content Security Policy به برنامههای ASP.NET
روشی که با هر دو برنامههای وب فرم و MVC کار میکند، تهیه یک HTTP module است؛ به شرح ذیل:
و یا در برنامههای ASP.NET MVC میتوان یک فیلتر جدید را تعریف کرد و سپس آنرا به صورت عمومی معرفی نمود:
در ماژول تهیه شده چند مورد دیگر را نیز مشاهده میکنید:
الف) X-XSS-Protection مربوط است به IE 8 به بعد
ب) تنظیم هدر X-Frame-Options به SameOrigin سبب میشود تا صفحات سایت شما دیگر توسط Iframeها در سایتهای دیگر قابل نمایش نباشد و فقط در سایت جاری بتوان صفحهای را از همان دومین در صورت نیاز توسط Iframeها نمایش داد.
ج) تنظیم X-Content-Type-Options به nosniff سبب میشود تا IE سعی نکند با اجرای یک محتوا سعی در تشخیص mime-type آن کند و به این ترتیب امنیت دسترسی و مشاهده اشیاء قرار گرفته در صفحه (و یا تزریق شده توسط مهاجمین) به شدت بالا خواهد رفت.
برای مطالعه بیشتر
Security through HTTP response headers
پروژهی کاملی مخصوص افزودن هدرهای یاد شده
https://nwebsec.codeplex.com/
یک نکته تکمیلی
توصیه شدهاست تا دیگر از روال رویدادگردان PreSendRequestHeaders برای ارسال هدرها استفاده نکنید؛ چون با پردازشهای غیرهمزمان تداخل ایجاد میکند.
غیرفعال کردن اجرای اسکریپتهای inline
عمدهی حملات XSS زمانی قابلیت اجرا پیدا میکنند که مهاجم بتواند به طریقی (ورودیهای اعتبارسنجی نشده)، اسکریپتی را به درون صفحهی جاری تزریق کند. بنابراین اگر ما به مرورگر اعلام کنیم که دیگر اسکریپتهای inline را پردازش نکن، سایت را تا حد زیادی در مقابل حملات XSS مقاوم کردهایم. این قابلیت به صورت پیش فرض خاموش است؛ چون به طور قطع فعال سازی آن بسیاری از سایتهایی را که عادت کردهاند اسکریپتهای خود را داخل صفحات وب مدفون کنند، از کار میاندازد. این نوع سایتها باید به روز شده و اسکریپتها را از طریق فایلهای خارجی js، به سایت و صفحات خود الحاق کنند.
برای فعال سازی این قابلیت، فقط کافی است هدرهای زیر به Response اضافه شوند:
Content-Security-Policy: script-src 'self' X-WebKit-CSP: script-src 'self' X-Content-Security-Policy: script-src 'self'
بعد از فعال شدن این قابلیت، فقط اسکریپتهایی که از طریق دومین شما به صفحه الحاق شدهاند، قابلیت اجرا را خواهند یافت و کلیه اسکریپتهای مدفون شده داخل صفحات، دیگر اجرا نخواهد شد. در این حالت اگر از CDN برای الحاق اسکریپتی استفاده میکنید، مثلا مانند الحاق jQuery به صفحه، نیاز است مسیر آنرا صراحتا در این هدر ذکر کنید:
Content-Security-Policy: script-src 'self' https://youcdn.com X-WebKit-CSP: script-src 'self' https://yourcdn.com X-Content-Security-Policy: script-src 'self' https://yourcdn.com
Content-Security-Policy: default-src 'self' https://youcdn.com X-WebKit-CSP: default-src 'self' https://yourcdn.com X-Content-Security-Policy: default-src 'self' https://yourcdn.com
نحوهی اضافه کردن هدرهای Content Security Policy به برنامههای ASP.NET
روشی که با هر دو برنامههای وب فرم و MVC کار میکند، تهیه یک HTTP module است؛ به شرح ذیل:
using System; using System.Web; namespace AntiXssHeaders { public class SecurityHeadersConstants { public static readonly string XXssProtectionHeader = "X-XSS-Protection"; public static readonly string XFrameOptionsHeader = "X-Frame-Options"; public static readonly string XWebKitCspHeader = "X-WebKit-CSP"; public static readonly string XContentSecurityPolicyHeader = "X-Content-Security-Policy"; public static readonly string ContentSecurityPolicyHeader = "Content-Security-Policy"; public static readonly string XContentTypeOptionsHeader = "X-Content-Type-Options"; } public class ContentSecurityPolicyModule : IHttpModule { public void Dispose() { } public void Init(HttpApplication app) { app.BeginRequest += AppBeginRequest; } void AppBeginRequest(object sender, EventArgs e) { var app = (HttpApplication)sender; var response = app.Context.Response; setHeaders(response); } private static void setHeaders(HttpResponse response) { response.Headers.Set(SecurityHeadersConstants.XFrameOptionsHeader, "SameOrigin"); // For IE 8+ response.Headers.Set(SecurityHeadersConstants.XXssProtectionHeader, "1; mode=block"); response.Headers.Set(SecurityHeadersConstants.XContentTypeOptionsHeader, "nosniff"); //todo: Add /Home/Report --> public JsonResult Report() { return Json(true); } const string cspValue = "default-src 'self';"; // For Chrome 16+ response.Headers.Set(SecurityHeadersConstants.XWebKitCspHeader, cspValue); // For Firefox 4+ response.Headers.Set(SecurityHeadersConstants.XContentSecurityPolicyHeader, cspValue); response.Headers.Set(SecurityHeadersConstants.ContentSecurityPolicyHeader, cspValue); } } }
//// RegisterGlobalFilters -> filters.Add(new ContentSecurityPolicyFilterAttribute()); public class ContentSecurityPolicyFilterAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var response = filterContext.HttpContext.Response; response.AddHeader("Content-Security-Policy", "script-src 'self'"); // the rest ... base.OnActionExecuting(filterContext); } }
الف) X-XSS-Protection مربوط است به IE 8 به بعد
ب) تنظیم هدر X-Frame-Options به SameOrigin سبب میشود تا صفحات سایت شما دیگر توسط Iframeها در سایتهای دیگر قابل نمایش نباشد و فقط در سایت جاری بتوان صفحهای را از همان دومین در صورت نیاز توسط Iframeها نمایش داد.
ج) تنظیم X-Content-Type-Options به nosniff سبب میشود تا IE سعی نکند با اجرای یک محتوا سعی در تشخیص mime-type آن کند و به این ترتیب امنیت دسترسی و مشاهده اشیاء قرار گرفته در صفحه (و یا تزریق شده توسط مهاجمین) به شدت بالا خواهد رفت.
برای مطالعه بیشتر
Security through HTTP response headers
پروژهی کاملی مخصوص افزودن هدرهای یاد شده
https://nwebsec.codeplex.com/
یک نکته تکمیلی
توصیه شدهاست تا دیگر از روال رویدادگردان PreSendRequestHeaders برای ارسال هدرها استفاده نکنید؛ چون با پردازشهای غیرهمزمان تداخل ایجاد میکند.
بازخوردهای دوره
بوت استرپ (نگارش 3) چیست؟
یک نکته تکمیلی در مورد فایل respond.min.js
این فایل را اگر به صورت معمولی در یک صفحه html بارگذاری شده از فایل سیستم اجرا کنید، پیام access is denied را دریافت خواهید کرد. علت این است که درخواستهای xmlHttpRequest آن برای اجرا نیاز به وب سرور دارند. بنابراین برای مواجه نشدن با این خطا، بهتر است مثال را در یک برنامه معمولی ASP.NET آزمایش کنید.
این فایل را اگر به صورت معمولی در یک صفحه html بارگذاری شده از فایل سیستم اجرا کنید، پیام access is denied را دریافت خواهید کرد. علت این است که درخواستهای xmlHttpRequest آن برای اجرا نیاز به وب سرور دارند. بنابراین برای مواجه نشدن با این خطا، بهتر است مثال را در یک برنامه معمولی ASP.NET آزمایش کنید.
بعد از لاگین کردن و فراخوانی وب سرویس هایم پیغام 431 (Request Header Fields Too Large) در کنسول مرورگر نمایش میدهد
باید اندازهی request headerها رو بیشتر کرد ؟ چگونه میتوان این کار را انجام داد؟
در این بخش قصد داریم سئو را بر روی یک برنامهی نوشته شده با آنگلولار و Asp.net Mvc اعمال نماییم. انگولار جیاس، صفحات را با استفاده از جاوااسکریپت رندر میکند، ولی اکثر کرالرها نمیتوانند جاوااسکریپت را اجرا کنند و موقع اجرای صفحات سایت ما فقط یک div خالی را میبینند.
کاری که سرویس Prerender یا فیلتر سفارشی AjaxCrawlable برای ما انجام میدهد، درخواستهایی را که از طرف کرالرها آمدهاست را شناسایی میکند و مانند یک مرورگر، با استفاده از phantomjs آنرا اجرا میکند و نتیجهی کامل صفحات ما را به صورت اچ تی ام ال استاتیک برمیگرداند.
فانتوم جی اس، موتور اختصاصی برای شبیه سازی مرورگر مبتنی بر Webkit میباشد. فانتوم جی اس را میتوانید بر روی ویندوز، لینوکس و مک نصب نمایید. فانتوم جی اس یک Console در اختیار برنامه نویس قرار میدهد که میتوان توسط آن، برنامههای جاوااسکریپت را اجرا نمود. همچنین فانتوم جی اس میتواند اسکرین شاتی را نیز از محتوای وب سایت ما فراهم نماید.
برای اینکه صفحات انگولار جی اس،ایندکس شوند سه مرحله وجود دارند:
1- به کرالر اطلاع دهیم که رندر کردن سایت، توسط جاوااسکریپت انجام میگردد؛ با اضافه کردن متاتگ زیر در اچ تی ام ال سایت (البته در حالت استفاده HTML5 push state ) :
<meta name="fragment" content="!"> <base href="/">
http://www.example.com/?_escaped_fragment_=
ما در این مثال از HTML5 push state استفاده میکنیم. بنابراین لینکی مانند http://www.example.com/user/123 توسط کرالر به صورت زیر دیده میشود:
http://www.example.com/user/123?_escaped_fragment_=
3- اچ تی ام ال کاملا رندر شده توسط سایت ما به کرالر ارسال گردد.
برای رندر کردن اچ تی ام ال صفحات، چندین روش وجود دارد:
روش اول: میتوانیم از سرویسهای آمادهای همچون Prerender.io استفاده کنیم که سرویسهایی را برای زبانهای مختلف ارائه کردهاند. باتوجه به توضیحات نمونه استفاده از آن در Asp.Net Mvc کافیست در سایت Prerender.io ثبت نام کرده، Token را دریافت کنیم و در کانفیگ برنامه قرار دهیم و در کلاس PreStart قطعه کد زیر را قرار دهیم:
DynamicModuleUtility.RegisterModule(typeof(Prerender.io.PrerenderModule));
یکی از ابزارهای مناسب تست کردن اینکه صفحات توسط کرالر ایندکس میشوند یا خیر، برنامه screamingfrog میباشد.
در پنل Ajax آن، صفحات ایندکس شده ما نمایش داده میشوند. لینکی مشابه زیر را در مرورگر اجرا کرده، با ViewPage Source کردن آن میتوانید نتیجه اچ تی ام ال کاملا رندر شده را مشاهده نمایید.
http://www.example.com/user/123?_escaped_fragment_=
نسخه رایگان سرویس Prerender.io تا 250 صفحه را پوشش میدهد.
روش دوم: فیلتر سفارشی AjaxCrawlable. در اولین قدم نیاز به نصب فانتوم جی اس داریم: <package id="PhantomJS" version="1.9.2" targetFramework="net452" /> <package id="phantomjs.exe" version="1.9.2.1" targetFramework="net452" />
برای اینکه خطای معروف A potentially dangerous Request.Form value was detected from the client را دریافت نکنیم، کافیست قسمتهایی از آدرس را که شامل کاراکترهای خاصی مانند :// میباشند، از url حذف کنیم و در اکشن returnHtml قسمتهای حذف شده را به url اضافه نماییم.
کرالرها با مشاهده تگ fragment، تمام لینکها را به همراه کوئری استرینگ _escaped_fragment_ میفرستند، که ما در سرور باید آنرا با رشته خالی جایگزین نماییم.
public class AjaxCrawlableAttribute : System.Web.Mvc.ActionFilterAttribute { private const string Fragment = "_escaped_fragment_"; public override void OnActionExecuting(ActionExecutingContext filterContext) { var request = filterContext.RequestContext.HttpContext.Request; var url = request.Url.ToString(); if (request.QueryString[Fragment] != null && !url.Contains("HtmlSnapshot/returnHTML")) { url = url.Replace("?_escaped_fragment_=", string.Empty).Replace(request.Url.Scheme + "://", string.Empty); url = url.Split(':')[1]; filterContext.Result = new RedirectToRouteResult( new RouteValueDictionary { { "controller", "HtmlSnapshot" }, { "action", "returnHTML" }, { "url", url } }); } return; } }
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "HtmlSnapshot", url: "HtmlSnapshot/returnHTML/{*url}", defaults: new { controller = "HtmlSnapshot", action = "returnHTML", url = UrlParameter.Optional }); routes.MapRoute( name: "SPA", url: "{*catchall}", defaults: new { controller = "Home", action = "Index" }) }
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new AjaxCrawlableAttribute()); } }
ایجاد کنترلر HtmlSnapshot و متد returnHTML :
Url را به عنوان آرگومان به تابع page.open فایل جاوااسکریپتی فانتوم میدهیم و بعد از اجرای کامل، خروجی را درViewData قرار میدهیم
public ActionResult returnHTML(string url) { var prefix = HttpContext.Request.Url.Scheme + "://" + HttpContext.Request.Url.Host+":"; url = prefix+url; string appRoot = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); var startInfo = new ProcessStartInfo { Arguments = string.Format("{0} {1}", Path.Combine(appRoot, "Scripts\\seo.js"), url), FileName = Path.Combine(appRoot, "bin\\phantomjs.exe"), UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, RedirectStandardInput = true, StandardOutputEncoding = System.Text.Encoding.UTF8 }; var p = new Process(); p.StartInfo = startInfo; p.Start(); string output1 = p.StandardOutput.ReadToEnd(); p.WaitForExit(); ViewData["result"] = output1.Replace("<!-- ngView: -->", "").Replace("ng-view=\"\"", ""); return View(); }
در فایل renderHtml.cshtml
@{ Layout = null; } @Html.Raw(ViewBag.result)
ایجاد فایل seo.js در پوشه Scripts سایت :
در این بخش webpage را ایجاد میکنیم و آدرس صفحه را از[system.args[1 دریافت کرده و عملیات کپچر کردن را آغاز میکنیم و بعد از تکمیل اطلاعات در سرور، کد زیر اجرا میشود:
console.log(page.content)
var page = require('webpage').create(); var system = require('system'); var lastReceived = new Date().getTime(); var requestCount = 0; var responseCount = 0; var requestIds = []; var startTime = new Date().getTime();; page.onResourceReceived = function (response) { if (requestIds.indexOf(response.id) !== -1) { lastReceived = new Date().getTime(); responseCount++; requestIds[requestIds.indexOf(response.id)] = null; } }; page.onResourceRequested = function (request) { if (requestIds.indexOf(request.id) === -1) { requestIds.push(request.id); requestCount++; } }; function checkLoaded() { return page.evaluate(function () { return document.all["compositionComplete"]; }) != null; } // Open the page page.open(system.args[1], function () { }); var checkComplete = function () { // We don't allow it to take longer than 5 seconds but // don't return until all requests are finished if ((new Date().getTime() - lastReceived > 300 && requestCount === responseCount) || new Date().getTime() - startTime > 10000 || checkLoaded()) { clearInterval(checkCompleteInterval); console.log(page.content); phantom.exit(); } } // Let us check to see if the page is finished rendering var checkCompleteInterval = setInterval(checkComplete, 300);
صفحه Layout.Cshtml
<!DOCTYPE html> <html ng-app="appOne"> <head> <meta name="fragment" content="!"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta charset="utf-8" /> <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" /> <meta name="viewport" content="width=device-width" /> <base href="/"> @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") <script src="~/Scripts/angular/angular.js"></script> <script src="~/Scripts/angular/angular-route.js"></script> <script src="~/Scripts/angular/angular-animate.js"></script> <script> angular.module('appOne', ['ngRoute'], function ($routeProvider, $locationProvider) { $routeProvider.when('/one', { template: "<div>one</div>", controller: function ($scope) { } }) .when('/two', { template: "<div>two</div>", controller: function ($scope) { } }).when('/', { template: "<div>home</div>", controller: function ($scope) { } }); $locationProvider.html5Mode({ enabled: true }); }); </script> </head> <body> <div id="body"> <section ng-view></section> @RenderBody() </div> <div id="footer"> <ul class='xoxo blogroll'> <li><a href="one">one</a></li> <li><a href="two">two</a></li> </ul> </div> </body> </html>
چند نکته تکمیلی:
* فانتوم جی اس قادر به اجرای لینکهای فارسی (utf-8) نمیباشد.
* اگر خطای syntax error را دریافت کردید ممکن است پروژه شما در مسیری طولانی در روی هارد دیسک قرار داشته باشد.
مطالب
خواندنیهای 12 مرداد
الگوهای طراحی برنامه نویسی شیءگرا
توسعه وب
دات نت فریم ورک
دبلیو پی اف و سیلور لایت
سی و مشتقات
کتابهای رایگان
متفرقه
محیطهای مجتمع توسعه
مرورگرها
پی اچ پی
نظرات مطالب
بررسی امنیتی، حین استفاده از jQuery Ajax
حذف هدر وب سرور بیشتر جهت مقابله با اسکنرهای خودکار میتونه کاربرد داشته باشه و عموم این اسکنرها هم کاری به Ajax ندارند یا درکی از آن ندارد (تا جایی که بررسی کردم).
اشتراکها
معرفی Mozilla's Fetch API
بله. مرورگرهایی که XMLHttpRequest Level 2 را پشتیبانی کنند (از IE 10 به بعد مثلا)، امکان Ajax upload توکار به همراه گزارش درصد آپلود را بدون نیاز به فلش یا سیلورلایت، دارند.یک نمونه پیاده سازی آن
نظرات مطالب
پلاگین DataTables کتابخانه jQuery - قسمت سوم
با سلام و عرض ادب
زمانیکه من با Ajax , Jquery سطرهای دیتای جدول مورد نظر برای DataTable شدن را از سمت سرور ایجاد میکنم متاسفانه بار اول دیتاها رو نشون میده ولی Search نمیکنه و صفحه بندی هم نمیکنه و ... در ضمن کدهای مربوطه رو هم میگذارم . لطفا راهنمایی کنید که اگه خواستیم دیتاها را از سمت سرور بیاریم و کار بده باید چه کار کرد؟ مرسی
:کد سمت سرور
زمانیکه من با Ajax , Jquery سطرهای دیتای جدول مورد نظر برای DataTable شدن را از سمت سرور ایجاد میکنم متاسفانه بار اول دیتاها رو نشون میده ولی Search نمیکنه و صفحه بندی هم نمیکنه و ... در ضمن کدهای مربوطه رو هم میگذارم . لطفا راهنمایی کنید که اگه خواستیم دیتاها را از سمت سرور بیاریم و کار بده باید چه کار کرد؟ مرسی
$(document).ready(function () { dataparam2 = "cmd=FillScope"; $.ajax({ url: "Default2.aspx", type: "POST", data: dataparam2, async: true, success: function (msg) { if (msg != '') { var data = eval("(" + msg + ")"); $("#tbodytblMain").html(''); for (var i = 0; i < data.length; i++) { $("#tbodytblMain").append( "<tr class='odd gradeX'>" + "<td style='width:200px'>" + data[i].T + "</td>" + "<td style='width:150px'>" + data[i].P + "</td>" + "<td>" + data[i].S + "</td>" + "<td>" + data[i].TP + "</td>" + "<td>" + data[i].Sp + "</td>" + "</tr>"); } } }, error: function (msg) { } }); $('#tblMain').dataTable(); });
if (Request["cmd"] == "FillScope") { string Val = "برخوار"; JavaScriptSerializer js = new JavaScriptSerializer(); string serText = ""; MUIDataClassesDataContext db = new MUIDataClassesDataContext(); var LST = (from x in db.tblProjectInfos where x.tblScope.xScopeName.Contains(Val) orderby x.tblScope.xScopeName select new { P = x.xPlace, S = x.tblScope.xScopeName, TP = x.tblProjectType.xProjectTypeName, Sp = x.tblStatus.xStatusName }); serText = js.Serialize(LST); Response.Write(serText); Response.End(); }