Is it possible to force/extend the routing engine to generate URLs in lower case, giving /controller/action instead of /Controller/Action?
What's more, you should force any incoming requests that are uppercase to be redirected to the lowercase version. Search engines treat URLs case-sensitively, meaning that if you have multiple links to the same content, that content's page ranking is distributed and hence diluted.
Returning HTTP 301 (Moved Permanently) for such links will cause search engines to 'merge' these links and hence only hold one reference to your content.
Add something like this to your Global.asax.cs file:
protected void Application_BeginRequest(object sender, EventArgs e)
{
// Don't rewrite requests for content (.png, .css) or scripts (.js)
if (Request.Url.AbsolutePath.Contains("/Content/") ||
Request.Url.AbsolutePath.Contains("/Scripts/"))
return;
// If uppercase chars exist, redirect to a lowercase version
var url = Request.Url.ToString();
if (Regex.IsMatch(url, #"[A-Z]"))
{
Response.Clear();
Response.Status = "301 Moved Permanently";
Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
Response.AddHeader("Location", url.ToLower());
Response.End();
}
}
Yes, just change it in the routing in the global.asax file.
#All asking if it matters: Yes I do think it matters. Having the url all in lower case just looks better.
Every time you don't make something look nice when you can, Bill Buxton kills a kitten.
Related
I have an mvc/forms hybrid webapplication hosted on a windows 2008 r2 instance on Azure. The webserver is IIS 7.5 . For the last 4-5 months my server is getting absolutely hammered by vulnerability scanners checking for php related vulnerabilities. example:
The controller for path '/wp-login.php' was not found or does not implement IController.
from Elmah
So I've gone in and specifically filtered .php and .cgi file extension requests in IIS 7.5 which is working great. However i am still getting hammered for requests like:
The controller for path '/admin/Cms_Wysiwyg/directive/' was not found or does not implement IController.
The controller for path '/phpmyadmin2018/' was not found or does not implement IController.
etc. etc. It's more an annoyance as everything is logged, a 404 is returned and it's all a useless resource throwaway.
Through Elmah i've queried a distinct list of URLs related to all these requests. What is the best way to short-circuit these requests? It would be good if i could optionally ban the IP's but right now there are 700 unique IPs making these requests in the last 3 months alone. Main priority is to just short circuit the requests from the dictionary of URLs I know are bogus and avoid the logging and response from my webserver. Thanks!
half pseudo code, but I think it will be helpful;
in Global.asax.cs:
public class MvcApplication : HttpApplication
{
protected void Application_BeginRequest(Object sender, EventArgs e)
{
var url = HttpContext.Current.Request.Url.AbsoluteUri;
if(checkUrl(url)){
// your code here
}
if (UserIsBanned(GetUserIp()))
{
Response.Write("ban");
Response.End();
}
}
private string GetUserIp()
{
return HttpContext.Current.Request.UserHostAddress;
}
Inside my Global.asax.cs I have the following simple code:
void Application_BeginRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Request.RawUrl.Contains("/oldControler/oldAction"))
{
HttpContext.Current.RewritePath("/newControler/newAction");
}
}
The above code causes redirection to "newControler/newAction" rendering the correct View, yet the URL that shows up in the browser is still "/oldControler/oldAction".
In case it's of any help, I inspected the properties inside the HttpContext.Current.Request after the RewritePath is applied and all of them, i.e FilePath, Path, ApplicationPath, URL, etc. show up correctly as "newControler/newAction", except for RawUrl which still shows up as "/oldControler/oldAction".
Any ideas on how to fix it?
That's how RewritePath works, it doesn't change the URL in the browser address bar and it's usually used when the old URL is more user friendly than the new URL.
If you also want to change the URL in the browser address bar, you can try
HttpContext.Current.Response.Redirect("/newControler/newAction");
or this if you also want to return HTTP 301 response code to the browser
HttpContext.Current.Response.RedirectPermanent("/newControler/newAction");
You might try:
HttpContext.Current.Response.RedirectLocation("/newControler/newAction");
I have an Owin/NancyFx single-page application using AngularJs and UI Router.
Its hosted in IIS7 and for the most part everything is working. However there is one annoying issue with the root path that I can't seem to solve.
I would like a trailing slash on the root path, something like:
http://myserver.internaldomain.com/myapp/
This way when UI Router goes to handle the hashbang routing, all urls will look like:
http://myserver.internaldomain.com/myapp/#/mySpaRoute
However, I can't seem to get a trailing slash to append, so instead my URL looks like:
http://myserver.internaldomain.com/myapp#/mySpaRoute
I have tried to create an Owin middleware the looks at the URL and redirects if there's a missing / at the end. This works for all routes that are handled by the WebApi but not NancyFx. That seems reasonable since NancyFx takes over routing early to handle rendering its views.
Next I tried a NancyFx BeforeRequest pipeline lambda to do the same thing, interrogate the URL and append a / as needed. This however resulted in a redirect loop. The request would come in to the pipeline as: http://example.com/app, and then redirect to: http://example.com/app/, however at the next pipeline execution, the trailing / would be stripped and the pipeline handler would redirect again -- this is where the loop occured.
So I guess simply, how do I make NancyFx add a trailing / to the end of my routes?
Update:
Went to lunch, talked to the duck a bit, updated all the assemblies, then decided that its just the root get path that I really need to append the / to make hashbang routing look decent:
public class HomeModule : NancyModule
{
// note this works fine when running from localhost, but when running
// as an application in IIS, a redirect loop occurs
public HomeModule()
{
Get["/"] = _ =>
{
var requestUri = new Uri(Request.Url);
if (!requestUri.AbsoluteUri.EndsWith("/"))
{
var targetUri = requestUri.ToString() + "/";
return Response.AsRedirect(targetUri);
}
const string view = "views/home.cshtml";
var model = new { Title = Constants.ApplicationTitle };
return View[view, model];
}
}
}
Annnnnnd Redirect loop.
Ultimately this appears to have been caused by the Uri class. The Uri class does a very good job of removing trailing slashes in many cases. This means that I was, essentially, fixing any "malformed" urls by creating a new Uri out of them. Then I was breaking these nice Uri's by appending a / to them. On redirect the newly cast Uri would remove my extraneous /, then fail the if statement and the process would begin again, hence by redirect loop.
To fix the issue, I instead used the System.Web.HttpContextBase property provided in the owin environment context and checked the Request.Url property which seems to be the original requested Url with little or no post-processing.
These changes were made in my EnforceTrailingSlashMiddleware that I had written earlier. Here is the invoke method:
public override async Task Invoke(IOwinContext context)
{
var httpContext = context.Environment["System.Web.HttpContextBase"] as System.Web.HttpContextBase;
if (httpContext != null && httpContext.Request != null && httpContext.Request.Url != null)
{
var path = httpContext.Request.Url.ToString();
/*
formatter is a class ("SlashFormatter") with two methods:
"ShouldAppendSlash" which takes a path string and returns a boolean
(whether or not a slash should be appended)
"AppendSlash" which takes a string, safely appends a slash and
then returns the modified string.
*/
if (formatter.ShouldAppendSlash(path))
{
var url = formatter.AppendSlash(path);
context.Response.Redirect(url);
}
}
await Next.Invoke(context);
}
Ever since the upgrade from MVC4 to MVC5, I have noticed an extra server header added to my web pages:
X-Frame-Options: SAMEORIGIN
I understand security benefits of adding this tag, but one of the pages is meant to be included inside an iframe from other projects (on other domains), this extra header is preventing this.
I have verified it is not the hosting IIS7 server that is adding the header, and when I downgraded back to MVC4 - the header is gone.
Does anyone know how to remove this default from MVC5?
MVC5 automatically adds the HTTP header X-Frame-Options with SAMEORIGIN. This prevents your site from being loaded into an iframe.
But we can turn this off in Application_Start in the Global.asax.cs.
Example
protected void Application_Start()
{
AntiForgeryConfig.SuppressXFrameOptionsHeader = true;
}
Update
I have written a post about this MVC5 prevents your website being loaded in an IFRAME
Try something like this in Global.asax:
protected void Application_PreSendRequestHeaders(object sender, EventArgs e)
{
HttpContext.Current.Response.Headers.Remove("X-Frame-Options");
}
EDIT:
Look at answer of Colin Bacon. It is more correct than mine.
In short - don't remove this header if you don't want to run your site in IFRAME because it will open forgery vulnerability. But if you still want to remove it - use AntiForgeryConfig.SuppressXFrameOptionsHeader = true; in Application_Start, it is more cleaner way for doing this.
If you want a little more flexibility, here's an ActionAttribute that adds/removes headers based on a whitelist. If the referrer isn't in the whitelist, then the SAMEORIGIN header is left in place. I was going to paste the code, but SO complains about the length.
https://long2know.com/2016/06/asp-net-anti-forgery-xframe-options/
Personally, I don't think it's a good idea to disable the X-Frame-Options across the whole site.I've created an ASP.NET MVC filter which removes this header and I simply apply this filter to the portions of the site that are used in iFrames e.g. widgets.
public class AllowDifferentOrigin : ActionFilterAttribute, IActionFilter
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
filterContext.HttpContext.Response.Headers.Remove("X-Frame-Options");
base.OnResultExecuted(filterContext);
}
}
Here is a replacement Extension method for the HtmlHelper class. It will first clear all X-Frame-Options headers and then add back a single X-Frame-Options header normally added by the built-in AntiForgeryToken method.
This technique respects the SuppressXFrameOptionsHeader setting, but has the downside of removing all previously added X-Frame-Options headers, even those with values other than SAMEORIGIN.
public static MvcHtmlString AntiForgeryTokenSingleHeader(this HtmlHelper html)
{
string token = AntiForgery.GetHtml().ToString();
HttpResponseBase httpResponse = html.ViewContext.HttpContext.Response;
httpResponse.Headers.Remove("X-Frame-Options");
if (!AntiForgeryConfig.SuppressXFrameOptionsHeader)
{
httpResponse.AddHeader("X-Frame-Options", "SAMEORIGIN");
}
return new MvcHtmlString(token);
}
Ok, so we have the RequireHttpsAttribute that we can use to ensure that a controller/controller method can only be called over SSL. In the case that we try to hit the method over HTTP, the server issues a 302 to the HTTPS version of the same controller (method).
This implies to my users that it is acceptable to issue the first request insecurely in the first place. I don't feel that this is acceptable. Before I trot out an attribute that issues a 404/500 status code in the case that the HTTP version is hit, does such an attribute already exist?
Before I trot out an attribute that issues a 404/500 status code in
the case that the HTTP version is hit, does such an attribute already
exist?
No, such attribute doesn't exist out of the box.
If the simply act of requesting the page using HTTP is not compromising any user data, I'd say the redirect should be enough and a perfect approach for your scenario. Why bother user with things we can take care of?
This implies to my users that it is acceptable to issue the first
request insecurely in the first place. I don't feel that this is
acceptable. Before I trot out an attribute that issues a 404/500
status code in the case that the HTTP version is hit, does such an
attribute already exist?
If you don't want your application to work at all for these URLs using http:// instead of https://, don't serve anything at all (404 or no connection).
Note that it's ultimately the user's responsibility to check that SSL/TLS is used (and used correctly with a valid certificate). Make sure the links to those address use https:// indeed, and that the users expect https:// to be used, at least for the start page. You could consider using HSTS if their browser support it (or possibly permanent redirects to the entry point that would be cached).
From another comment:
I don't want any info about the url leaked in any way to any third parties
Once the request has been made using this http:// URL from the client, there's little point doing anything on the server. It's too late: an eavesdropper could have seen the request. (If your own page doesn't link to external websites, they wouldn't see that address in the referrer either.)
Even if your server doesn't even listen on the plain HTTP port, an active MITM attacker (or more simply, a proxy) could potentially listen to that request and get the URL, without it even reaching your server.
Again: make sure your users expect https:// to be used, and once they're on a secure page, make sure your links/form actions to other sections of your site all use https://.
So for reference, here's my new attribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
Inherited = true,
AllowMultiple = false)]
public class HttpsOnlyAttribute : FilterAttribute, IAuthorizationFilter
{
private readonly bool disableInDebug;
public HttpsOnlyAttribute(bool disableInDebug = false)
{
this.disableInDebug = disableInDebug;
}
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
#if DEBUG
if (disableInDebug) return;
#endif
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
var context = filterContext.HttpContext;
var request = context.Request;
var isSecure = request.IsSecureConnection;
if (!isSecure)
{
throw new HttpException(404, "Not found");
}
}
}