Fastest way to rewrite only one subpath - asp.net

I have a website developed in Classic ASP.NET and running on IIS 7.5. It works okay. But now I have the task to add affiliate program to my website. I want my reflink to looks like www.mywebsite.com/r/userid. Well, I googled around and found I can:
Use UrlRewrite third-party HttpModules. As I understand, they are based on runAllManagedModulesForAllRequests="true" web.config setting. Theoretically, I can:
Set runAllManagedModulesForAllRequests="true" and do RewritePath in Application_BeginRequest manually. But my Application_BeginRequest already contains a bit of code. It is too heavy to send all pages, images etc. to Application_BeginRequest because of one rarely called URL.
So, the question is how can I rewrite only subpath that starts with www.mywebsite.com/r/, and do not call Application_BeginRequest for every image, css etc.? Best if no third-party things.

Well, finished up with HttpModule.
web.config:
<system.webServer>
<modules>
<add name="ReflinkModule" preCondition="" type="www.ReflinkModule" />
</modules>
</system.webServer>
ReflinkModule.cs:
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(BeginRequest);
}
public void BeginRequest(Object source, EventArgs e)
{
if (HttpContext.Current == null)
return;
if (HttpContext.Current.Request == null)
return;
if (HttpContext.Current.Request.RawUrl == null)
return;
string start = "/r/";
if (!HttpContext.Current.Request.RawUrl.StartsWith(start))
return;
string key = HttpContext.Current.Request.RawUrl.Substring(start.Length);
HttpContext.Current.RewritePath("~/Xxxxx.aspx?r=" + key);
}

Related

URL Rewriting exceptional case for removing .aspx extension

My scenario is: I have a website which is ASP.NET WebForm. Users can create their own page on my web site, their page url would be something like this: (MyWebsite.com/UserPage). but It is actually: (MyWebsite.com/UserPages.aspx?q=UserPage). It means when you enter the url (MyWebsite.com/UserPage) It rewrites the url and shows you (MyWebsite.com/UserPages.aspx?q=UserPage) (but the address bar is always like (MyWebsite.com/UserPage).
Here's my code in my "UrlRewriting" class:
void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
if (app.Request.Path.Contains("/") && !app.Request.Path.Contains(".") && app.Request.Path.IndexOf("/") == app.Request.Path.LastIndexOf("/"))
{
string userPageTitle = app.Request.Path.Substring(app.Request.Path.IndexOf("/") + 1);
if (!string.IsNullOrEmpty(userPageTitle ))
{
app.Context.RewritePath(string.Format("UserPages.aspx?q={0}", userPageTitle));
}
}
}
Now here's my problem: as I said my project is ASP.NET WebForm, (So, all of pages have .aspx extension) I wanted to remove the .aspx extension in my Urls, I've tried some codes in web.config which were working properly (In normal cases), but In my case, if you enter (MyWebsite.com/UserPage) It will be considering this "UserPage", as "UserPage.aspx". How can I handle this?
I usually do this with Routing which is available in ASP.NET Web Forms 4+.
You register your routes (URL patterns) in Global.asax, and specify which ASPX page will handle that URL.
This example would have UserPage.aspx handle all URLs that weren't otherwise handled by other ASPX pages.
void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapPageRoute("UserPageRoute", "{*url}", "~/UserPage.aspx");
}
Then in your UserPage.aspx you can determine the URL requested by looking at the Request.Url object, eg. Request.Url.PathAndQuery.
Note that you may need some extra web.config settings for this to work, eg (to manage extensionless URL requests)...
<configuration>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />

Add custom response header to web.config

I have a website that is susceptible to a clickjacking vulnerability. Doing some research, it looks like one of the simple approaches is to simply add the X-Frame-Options: SAMEORIGIN to the response header. This is a very old web application (ca. last update was 2004), and is running IIS 6 with ASP.NET 2.0.
In newer versions, I could simply add the following section to the web.config
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="X-Frame-Options" value="SAMEORIGIN" />
</customHeaders>
</httpProtocol>
</system.webServer>
And that would be the end of it. However, I can't seem to be able to verify that this is possible using IIS 6.
Is this possible with IIS 6 and ASP.NET 2.0 to be done in only the web.config file? If so, how? If not, what code changes would I have to make in order to achieve the same result? Would simply adding
Context.Response.AddHeader("X-Frame-Options", "SAMEORIGIN");
to the Global.asax#Application_EndRequest be sufficient?
I don't believe that you'll be able to accomplish this solely by updating the web.config since you are targeting II6 (as support for the <customHeaders> section was added in IIS7+).
What you would likely need to do would be to create a custom HttpModule similar to the approach mentioned in this blog post that would handle actually adding the Header which might look something like this :
public class SameOriginHeaderModule : IHttpModule
{
private HttpApplication _application;
public void Init(HttpApplication context)
{
_application = context;
context.PreSendRequestHeaders += OnPreSendRequestHeaders;
}
void context_EndRequest(object sender, EventArgs e)
{
// If your request exists, add the header
if (_application.Context != null)
{
var response = _application.Response;
response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
}
}
public void Dispose()
{
}
}
You would then need to register this module within your web.config file as seen below :
<configuration>
<system.web>
<httpModules>
<add name="SameOriginHeaderModule" type="SameOriginHeaderModule" />
</httpModules>
</system.web>
</configuration>

Block IP Addresses In HttpModule

I have taken over a domain that had was running an old version of Community Server. Needless to say the bots are spamming me trying to find holes.
I'd like to block entire IP blocks before System.Web.HttpRequest.ValidateInputIfRequiredByConfig() is fired. I have a IHttpModule that I have tried but I assume it's getting called after because HealthMonitoring is catching the Exceptions. Here is the module:
public class IpBlockerModule : IHttpModule
{
private static readonly string[] Hacks = new[]
{
"60.169.73.",
"60.169.75.",
"61.160.232.",
"61.160.207.",
"92.85.161."
};
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.BeginRequest += (Application_BeginRequest);
}
private void Application_BeginRequest(object source, EventArgs e)
{
var context = ((HttpApplication) source).Context;
var ipAddress = context.Request.UserHostAddress;
if (!IsHackIpAddress(ipAddress))
{
context.Response.StatusCode = 403; // (Forbidden)
}
}
private static bool IsHackIpAddress(string ip)
{
if (ip == null) return true;
return Hacks.Any(x => x.StartsWith(ip));
}
}
And the relevent web.config sections:
<system.web>
<httpModules>
<add name="IpBlockerModule" type="MyNameSpace.IpBlockerModule" />
</httpModules>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<modules runAllManagedModulesForAllRequests="true" >
<add name="IpBlockerModule" type="MyNameSpace.IpBlockerModule" preCondition="" />
</modules>
</system.webServer>
The reasoning behind this is my inbox is getting spammed from all the
A potentially dangerous Request.Path value was detected from the
client
and
A potentially dangerous Request.Form value was detected from the client
notifications. Is something wrong with my Module, or am I correct in assuming modules don't get fired until after the fact?
As an alternative solution have you considered letting IIS do the work for you? This way the request never makes it to your application. You can do this via the web.config and there's an article detailing the process located here. The following example is copied directly from that article and would be placed inside the <system.webServer> section of your web.config:
<security>
<ipSecurity allowUnlisted="true"> <!-- this line allows everybody, except those listed below -->
<clear/> <!-- removes all upstream restrictions -->
<add ipAddress="83.116.19.53"/> <!-- blocks the specific IP of 83.116.19.53 -->
<add ipAddress="83.116.119.0" subnetMask="255.255.255.0"/> <!--blocks network 83.116.119.0 to 83.116.119.255-->
<add ipAddress="83.116.0.0" subnetMask="255.255.0.0"/> <!--blocks network 83.116.0.0 to 83.116.255.255-->
<add ipAddress="83.0.0.0" subnetMask="255.0.0.0"/> <!--blocks entire /8 network of 83.0.0.0 to 83.255.255.255-->
</ipSecurity>
</security>
You can also add the ability to get and log IP addresses so as to identify and block only the spammy ones.
Here's C# code to get IP addresses
string ipadd;
ipadd = Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (ipadd == "" || ipaddress == null)
ipadd = Request.ServerVariables["REMOTE_ADDR"];
I noticed that the link in the answer above is dead, so use this well-detailed article here

Using Routing without MVC: authentication form

Now I'm trying to work with System.Web.Routing. All is just fine, but I can't understand how to make form authentication work with url routing (return url, redirection, etc). Google says nothing. Help! :)
UPD: I forgot - I don't use MVC. That's the problem. How to use rounig and form authentication without MVC
UPD2: more about my problem
What I want to get: urls such “mysite.com/content/123”, “mysite.com/login/”, etc using Routes. It’s important to make login page works like “regular” ASP.NET login form (redirects to login from secure area when not login on, and redirect back to secure area when loggined).
That’s what I’m doing.
In global.asax on Application_Start, register routes like this:
routes.Add("LoginPageRoute", new Route("login/", new CustomRouteHandler("~/login.aspx")));
routes.Add("ContentRoute", new Route("content/{id}", new ContentRoute("~/content.aspx"))
{
Constraints = new RouteValueDictionary {{ "id", #"\d+" }}
});
Where CustomRouteHandler and ContentRoute – simple IRouteHandler classes, just like:
...
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var page = BuildManager.CreateInstanceFromVirtualPath(VirtualPath, typeof(Page)) as IHttpHandler;
return page;
}
...
All seems to be perfect: I’m getting content.aspx when go to “/content/10” and login.aspx when go to “/login/”. But…
When I make content secured (in web.config, with deny=”?”), login form doesn’t work like expected.
Now I can’t reach the “/content/10” page:
0. I’m typing “/content/10” in my browser.
1. Site redirects to “/login/?ReturnUrl=%2fcontent%2f10”. (Hm… seems like all problems starts here, right? :)
2. I’m trying to log in. No matter what credentials I’m entered…
3. …site redirects me to “login?ReturnUrl=%2fContent%2f10” (yellow screen of error - Access is denied. Description: An error occurred while accessing the resources required to serve this request. The server may not be configured for access to the requested URL.)
So, the problem is how to get ASP.NET understand real ReturnUrl and provide redirection after login.
These steps should allow you to implement the required behaviour.
To summarize:
You are using routing but not MVC. My example will map a url like http://host/Mysite/userid/12345 onto a real page at http://host/Mysite/Pages/users.aspx?userid=12345.
You want to control access to these addresses, requiring the user to logon. My example has a page http://host/Mysite/login.aspx with a standard login control, and the site is configured to use forms authentication.
Step 1
I've "hidden" the contents of the Pages folder using this web.config in the Pages folder:
<?xml version="1.0"?>
<configuration>
<system.web>
<httpHandlers>
<add path="*" verb="*"
type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
<pages validateRequest="false">
</pages>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<handlers>
<remove name="BlockViewHandler"/>
<add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler"/>
</handlers>
</system.webServer>
</configuration>
This ensures that if anyone uses a url like http://host/Mysite/Pages/users.aspx?userid=12345, then they receive a standard 404 response.
Step 2
My top level web.config file contains (as well as all the standard stuff) this location element:
<location path="userid">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>
This prevents anonymous access to urls of the form http://host/Mysite/userid/12345 which means users will be automatically redirected to login.aspx, then if they provide valid credentials, they will be redirected to the correct location.
Step 3
For reference here is my global.asax:
<script RunAt="server">
void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.RouteExistingFiles = true;
routes.Add("UseridRoute", new Route
(
"userid/{userid}",
new CustomRouteHandler("~/Pages/users.aspx")
));
}
</script>
And here is my route handler:
using System.Web.Compilation;
using System.Web.UI;
using System.Web;
using System.Web.Routing;
using System.Security;
using System.Web.Security;
public interface IRoutablePage
{
RequestContext RequestContext { set; }
}
public class CustomRouteHandler : IRouteHandler
{
public CustomRouteHandler(string virtualPath)
{
this.VirtualPath = virtualPath;
}
public string VirtualPath { get; private set; }
public IHttpHandler GetHttpHandler(RequestContext
requestContext)
{
var page = BuildManager.CreateInstanceFromVirtualPath
(VirtualPath, typeof(Page)) as IHttpHandler;
if (page != null)
{
var routablePage = page as IRoutablePage;
if (routablePage != null) routablePage.RequestContext = requestContext;
}
return page;
}
}
The first result I got from a Google search is Frederiks excellent post on forms authentication in ASP.NET MVC. Note that the post was relevant for an early version of ASP.NET MVC, you will have to write and test the code.
HTH, Indy

HttpModule doesn't seem to work

I've created a simple HttpModule to log the uses of my existing webservice. There's a dll containing a single class
public class TrackingModule : System.Web.IHttpModule
{
public TrackingModule(){}
public void Init(System.Web.HttpApplication context)
{
context.BeginRequest+=new EventHandler(context_BeginRequest);
}
public void Dispose()
{
}
private void context_BeginRequest(object sender, EventArgs e)
{
try
{
Microsoft.ApplicationBlocks.ExceptionManagement.ExceptionManager.Publish( new Exception("Log attept") );
HttpApplication app = (HttpApplication)sender;
string method = app.Request.RawUrl;
SaveUseToDatabase( app.Request.UserHostAddress, method );
}
catch( Exception ex )
{
try
{
Microsoft.ApplicationBlocks.ExceptionManagement.ExceptionManager.Publish( ex );
}
catch{}
}
}
}
After compiling the dll I add it to webservice's bin folder and in webservice's web.config I add:
<system.web>
<httpModules>
<add name="TrackingModule" type="WebserviceTrackingModule.TrackingModule, WebserviceTrackingModule"/>
This works fine on my computer, but when I copy it to production server, nothing happens. No new entries in database, no entries logged by ExceptionManager. As if it's not there at all.
What can I be missing?
Edit:
After performing another test I can add that it works when I add it for a webservice that has it's own top-level virtual directory. It doesn't work for webservices that reside in virtual directories that are subfolders of another virtual directory.
I know that HttpModules settings are being inherited by subdirectories, but it looks like the existence of parent directory gets in the way.
I believe I have found a better solution. Attach the module at runtime instead of in the web config. Check out Rick Strahl's blog post for the details.
OK, I'm answering my own question.
It doesn't work when you define <httpModules> in subdirectory's web.config, even when the subdirectory is configured as an application. The only solution I found so far is to define them within <location> tag in web.config of root application (parent directory).
I don't like it :(
I found the answer to this question in http://forums.iis.net/t/1151924.aspx
oh well, a little
process-of-elimination never fails
me.
After staring at the 3.5-related
web.config code, I realized that my
module needed to be added to the new
section:
<system.webserver>
<modules>
instead of system.web...at least it's
working now.
So to translate that:
If you are having a problem with httphandlers
add your handler to the modules node in system.webserver and see if that works
Copy the format used for scriptmodule.
Does this work?
<add name="TrackingModule" type="WebserviceTrackingModule.TrackingModule" />
And is the context_BeginRequest method definitely being called for each request?

Resources