Tools
Fiddler
web debugging proxy
Fiddler can log all HTTP(S) traffic
between your computer and the Internet
inspect communications, set breakpoints, and modify outgoing HTTP requests or incoming
responses
tool can be extended using any .NET language
no configuration needed for IE or Chrome
may need to manually set proxy for FireFox
FireBug
open source add-on for FireFox
go to
http://getfirebug.com
with FireFox to install
IE9 Developer Toolbar
similar functionality to FireBug
major difference is the UI
Lens
free tool from
http://ethicalhackingaspnet.codeplex.com
only for testing ASP.NET apps
open source written in .NET with WPF UI & modular architecture
extensible
Understanding Session Management
cookies used to maintain state in a stateless environment
cookies passed in header section of traffic
minimal state is stored in cookie
contains ID of session object on server-side
simple session - used to connect HTTP request and response pairs together that originate
from the same client, no authentication
login session - starts when user signs in and ends when user signs out, login controls
& membership providers implement login sessions in ASP.NET
Attacking the ASP.NET Authentication
most attractive target
Deep Dive into ASP.NET Authentication
when using built-in login controls & OOB membership provider authentication
just works
under the hood
- Login control calls ValidateUser method of configured MembershipProvider
- if credentials are invalid, login process terminates & error message is displayed
-
if credentials are valid, Login control calls SetAuthCookie method of FormsAuthentication
class that reads settings from app.config before creating authentication cookie
& attaching the cookie to the HttpResponse
-
user is redirected with HTTP 302 status code to the original page, response contains
Set-Cookie HTTP header with default cookie name .ASPXAUTH
-
browser receives cookie and places it in its cookie store, when another request
is made of the same domain the cookie is attached to the new request
-
cookie is received by the FormsAuthenticationModule on the server, if cookie is
valid a new FormsIdentity object and a GenericPrinciple object and assigns them
to the current HttpContext
SetAuthCookie method creates the authentication cookie and puts a FormsAuthenticationTicket
object into the cookie
FormsAuthenticationTicket properties
CookiePath - directory where cookie is active
Expiration - date and time
Expired - boolean value
IsPersistent - boolean value
IssueDate - date and time
Name - login name of user
Version - numeric constant defining algorithms use to build the cookie
UserData - can be used for custom purposes
by default the ticket is hashed, encrypted & converted to a hex string before
it is added to the cookie
Stealing the Ticket
one method for attacker to get the ticket is to use XSS
cookie contains HttpOnly property
set to true by default, signals browser that cookie should not be accessed by any
script code
ticket transmitted in clear text, attacker doesn't have to decrypt ticket to use
it
protect ticket by using SSL
<authentication mode="Forms" >
<forms requireSSL="true" ... />
<authentication />
Tampering with the Ticket
by default ticket is encrypted with the machine key, attacked cannot read
ASP.NET adds secret server-side value to ticket hash, prevents attacker from altering
ticket
explicitly set in web.config
<authentication mode="Forms" >
<forms protection="All" ... />
<authentication />
Hijacking the Login Session
1 - start Fiddler to capture all traffic between browser and website
2 - open browser and sign-in to ASP.NET website
3 - inspect the HTTP 302 Redirect response for Set-Cookie HTTP header
4 - copy value of cookie to clipboard
5 - start Lens and enter URL of website
6 - switch to Session Fixation tab and enter the cookie name and value
7 - click Save To button for a different browser
8 - open other browser and visit website being tested, if website accepts cookie
the login session has been hijacked
Protecting Against Login Session Hijacking
problem is cookie not bound to a specific client
can use FormsAuthenticationTicket property UserData to hold additional data such
as User-Agent header or IP address
no perfect solution
to create custom cookie must over-ride default cookie-creation mechanism of ASP.NET
// Create a custom UserData field for the authentication token.
string userData = String.Format( CultureInfo.InvariantCulture, "{0}|{1}|{2}|{3}|{4}", clientIP, userAgent, role, userId, email );
// Because there is no direct way to build a standard ASP.NET authentication ticket with custom user data,
// decrypt the standard ticket from the standard authentication cookie, and build a new ticket with the
// default parameters with the additional custom user data.
// Build a standard ASP.NET forms authentication cookie.
HttpCookie cookie = FormsAuthentication.GetAuthCookie( userName, this.RememberMe.Checked );
// Decrypt the ticket from the standard cookie.
FormsAuthenticationTicket oldTicket = FormsAuthentication.Decrypt( cookie.Value );
// Create a new ticket with the default parameters plus the custom user data field.
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(
oldTicket.Version, oldTicket.Name, oldTicket.IssueDate, oldTicket.Expiration,
oldTicket.IsPersistent, userData );
// Replace the default cookie value with the encrypted ticket.
cookie.Value = FormsAuthentication.Encrypt( newTicket );
to redirect user to original page do not call the RedirectFromLoginPage method because
it creates the standard authentication cookie
string redirectUrl = Forms.Authentication.GetRedirectUrl(userName, false);
Response.Redirect(redirectUrl);
check cookies in global.asax method Application_PostAuthenticateRequest
protected void Application_PostAuthenticateRequest( object sender, EventArgs e )
{
// Get the principal set by the ASP.NET forms authentication module.
IPrincipal user = HttpContext.Current.User;
// If the user is successfully authenticated by the ASP.NET forms authentication module.
if( user.Identity.IsAuthenticated && user.Identity.AuthenticationType.Equals( "Forms", StringComparison.OrdinalIgnoreCase ) )
{
// Get the identity set by the ASP.NET forms authentication module.
FormsIdentity formsIdentity = user.Identity as FormsIdentity;
// Get the custom data from the authentication ticket.
string userData = formsIdentity.Ticket.UserData;
// Parse the custom user data in the ticket.
string[] userDataParts = userData.Split( '|' );
string clientIP = userDataParts[ 0 ];
string userAgent = userDataParts[ 1 ];
PortalRole role = (PortalRole) Enum.Parse( typeof( PortalRole ), userDataParts[ 2 ] );
int userId = Int32.Parse( userDataParts[ 3 ] );
string email = userDataParts[ 4 ];
// Get a shortcut to the current request.
HttpRequest req = HttpContext.Current.Request;
// Security check: check if the IP address of the client match the IP address stored in the authentication cookie.
if( !req.UserHostAddress.Equals( clientIP, StringComparison.OrdinalIgnoreCase ) )
{
throw new SecurityException( "Hack!" );
}
// Security check: check if the UserAgent field of the client match the UserAgent stored in the authentication cookie.
if( !req.UserAgent.Equals( userAgent, StringComparison.OrdinalIgnoreCase ) )
{
throw new SecurityException( "Hack!" );
}
// Create a PortalIdentity based on the authentication ticket supplied by the client.
MyPortalIdentity portalIdentity = new MyPortalIdentity(
user.Identity.Name, userId, role, email );
// Create a PortalPrincipal that corresponds to the PortalIdentity.
MyPortalPrincipal portalPrincipal = new MyPortalPrincipal( portalIdentity );
// Assign the custom principal to the HttpContext and the current thread to make it available to all parts of the application.
HttpContext.Current.User = portalPrincipal;
Thread.CurrentPrincipal = portalPrincipal;
}
}
Cross-Site Request Forgery
this URL and querystring will transfer $1000 to account 111-222-333 when the user
is authenticated
http://bank.example.com/Transfer?To=111-222-333&Amount=1000
server will check GET request for login cookie
a maliciously crafted URL
<img src="http://bank.example.com/Transfer?To=111-222-333&Amount=9999" >
this tag forces the GET
if the browser has a persistent cookie for bank.example.com it will be attached
to the GET request
Protecting Against Cross-Site Request Forgery
best option is not to use persistent cookie but the behavior not user-friendly
server can't distinguish between malicious & intentional requests
with values required by server known, attacker can build valid request beforehand
making all requests different can mitigate chances for successful attack
ASP.NET Post requests use view state
ViewStateUserKey property can hold arbitrary value tying current user to current
session
ViewStateUserKey property can only be set in Page_Init phase of page lifecycle
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if(this.User.Identity.IsAuthenticated)
{
this.ViewStateUserKey = this.SessionID;
}
}
raises barrier
pages must have view state enabled
ViewStateUserKey is not checked when view state Method Authentication Code (MAC)
validation is turned off
view state MAC is only checked for POST requests
can be defeated by passing view state in query string param to bypass MAC
when user's session times out runtime throws a view state MAC validation error
Additional Protection Against Cross-Site Request Forgery
situations where view state can't be used
use other parts of the HTTP traffic to make requests and responses unique
an example is to add value to hidden field
HttpModule implementation can be found at
http://www.owasp.org/index.php/.Ney_CSRF_Guard
using sessions only for CSRF protection is expensive
alternative is AntiCSRF HttpModule from
http://anticsrf.codeplex.com
for either of these methods ensure app is free from XSS vulnerabilities and that
GET requests are idempotent
Attacking the ASP.NET Session
ASP.NET session is managed by SessionStateModule HTTP module, independent of authorization
modules
module creates new session identifier
identifier stored either in HTTP cookie or in the URL depending upon cookieless
attribute of the sessionState tag in the web.config
in cookie identifier is not encrypted or hashed
many similarities between the authentication & the session
similar attacks can target the two
ASP.NET does not use any incremental sequence for session IDs, no guessing
Testing App Against Session Hijacking
1 - start Fiddler & do some action that will store a value in the session
2 - look for Set-Cookie HTTP header in the response or Cookie header named ASP.NET_SessionId
in the request
3 - copy 24 character value to clipboard
4 - Start Lens & enter the website URL in target URL textbox
5 - on Session Fixation tab check Cookie Name & paste clipboard value in Cookie
Value field
6 - click Save To button to create cookie in a different browser
7 - open other browser & navigate to website, if website accepts cookie the
session has been hijacked
Protect a Website Against Session Hijacking
problem is cookie not bound to specific client
solution similar to preventing log in session hijacking
SessionStateModule is not as extensible as FormsAuthenticationModule
create new class implementing IHttpModule
public class SecureSessionModule : IHttpModule
{
// Initializes a module and prepares it to handle requests.
public void Init( HttpApplication context )
{
// subscribe to the events
context.BeginRequest += new EventHandler( this.OnBeginRequest );
context.EndRequest += new EventHandler( this.OnEndRequest );
}
}
when a cookie is created it will be sent to the browser in HTTP response
access cookie in OnEndRequest handler using helper methods
public void OnEndRequest( object sender, EventArgs e )
{
// Get the session cookie value from the outgoing response.
HttpCookie cookie = this.GetCookie( HttpContext.Current.Response.Cookies );
// Add the MAC if a session cookie is present in the response.
if( cookie != null )
{
// Generate the new MAC.
string mac = this.GenerateMac( cookie.Value, HttpContext.Current.Request );
// Add the MAC to the response cookie.
cookie.Value += mac;
}
}
private HttpCookie GetCookie( HttpCookieCollection cookies )
{
// NOTE: Use "for" because "foreach" does not work ("Unable to cast object of type 'System.String' to type 'System.Web.HttpCookie'.")
for( int i = 0; i < cookies.Count; i++ )
{
HttpCookie cookie = cookies[ i ];
if( cookie.Name.Equals( "ASP.NET_SessionId", StringComparison.OrdinalIgnoreCase ) )
{
return cookie;
}
}
return null;
}
private string GenerateMac( string sessionID, HttpRequest request )
{
// Build the additional content to store in the cookie.
// NOTE: On some networks (AOL, mobile) the IP address may change frequently, and behind a proxy multiple clients can have the same IP address.
string content = String.Format( CultureInfo.InvariantCulture, "{0}|{1}",
request.UserHostAddress, request.UserAgent );
// The server side secret key for the HMAC.
// NOTE: This secret should be stored encrypted in the web.config.
byte[] key = Encoding.UTF8.GetBytes( "Any server side secret..." );
// Build the HMAC.
using( HMACSHA512 hmac = new HMACSHA512( key ) )
{
byte[] hash = hmac.ComputeHash( Encoding.UTF8.GetBytes( content ) );
return Convert.ToBase64String( hash );
}
}
when request comes in OnBeginRequest handler is invoked
public void OnBeginRequest( object sender, EventArgs e )
{
// Get the incoming HttpRequest.
HttpRequest request = HttpContext.Current.Request;
// Get the session cookie from the incoming request.
HttpCookie cookie = this.GetCookie( request.Cookies );
if( cookie != null )
{
// Get the cookie value.
string value = cookie.Value;
if( !String.IsNullOrEmpty( value ) )
{
// Get the session ID and the MAC from the cookie.
string sessionID = value.Substring( 0, 24 );
string clientMac = value.Substring( 24 );
// Generate a MAC on the server.
string serverMac = this.GenerateMac( sessionID, request );
// Compare the client and the server MACs.
if( !clientMac.Equals( serverMac, StringComparison.OrdinalIgnoreCase ) )
{
// Terminate the request but do not provide too much details for the attacker.
throw new SecurityException( "Hack!" );
}
// Remove the MAC from the cookie before ASP.NET uses it.
cookie.Value = sessionID;
}
}
}
add module to ASP.NET request processing pipeline
<configuration>
<system.web>
<httpModules>
<add name="SecureSessionModule" type="SecureSessionModule" />
</httpModules>
</system.web>
</configuration>
Session Fixation
attack doesn't need to steal session ID, can assign ID
1 - attacker uses uses browser vulnerability or external app to store new cookie
for a website in the cookie store, cookie is named ASP.NET_SessionId and a session
ID is assigned
2 - when website is visited the browser attaches the bogus cookie from its cookie
store
3 - attacker uses a copy of the same cookie
protection against session fixation
change name of cookie from ASP.NET default
use custom cookie
generate custom cookie with implementation of ISessionIDManager
extend SessionIDManager type to create custom cookie value
public class MySessionIdManager : System.Web.SessionState.SessionIDManager
{
public override string CreateSessionID(HttpContext context)
{
// create custom session ID here
}
public override bool Validate(string id)
{
// validation logic
}
}
configuration
<configuration>
<system.web>
<sessionState sessionIDManagerType="MySessionIdManager" />
</system.web>
</configuration>
Hacking the View State
any data placed in the view state is publicly visible on the client
view state serialized by using LosFormatter (Limited Object Serialization)
produces highly compact, ASCII format serialization
Testing View State Against Information Disclosure
1 - start LENS and enter full URL of site to be tested
2 - go to ViewState tab & click Extract button to download page, snip out content
of __VIEWSTATE hidden field
3 - click Decode button, lower pane uses treeview to display decoded content
4 - search view state for particular value using Keyword
Encrypting View State
built-in mechanism to encrypt view state content
ViewStateEncryptionMode property of page has three possible values
Auto - encryption only occurs if a control requests encryption by invoking Page
type's RegisterRequiresViewStateEncryption method
Always - view state is always encrypted
Never - no encryption
some controls by default may request encryption
FormView, ListView, GridView & DetailsView have DataKeyNames property, key field
values are stored in view state
setting DataKeyNames to a non-null value causes the control to request encryption
Tampering with the View State
view state has important role in event processing
change events don't cause immediate postback, event handlers are executed on postback
attackers can change state of controls & influence which event handlers are
executed on the server
ASP.NET protects view state against client-side modification with HMAC
<pages enableViewStateMac="true" ... />
Reposting the View State
ASP.NET runtime processes object graph & serializes type and value of the objects
does not store two important properties of the view state
timestamp when view state was created or any expiration date
identifier of the session or the user who visits the page
mitigate threat of tampering by using ViewStateUserKey property of page
number of MAC errors may increase
HttpException (0x90004005): Validation of viewstate MAC failed. If this
application is hosted by a Web Farm or cluster, ensure that
<machineKey> configuration specifies the same validationKey and
validation algorithm. AutoGenerate cannot be used in a cluster.
most common reason is page submission after timeout
workarounds
apply Post/Redirect/Get (PRG) pattern to avoid re-posts when the user refreshes
the page
maintain JavaScript timer on the client that notifies the user before the session
times out
must handle view state MAC exceptions gracefully
exception is general HttpException but inner exception is a ViewStateException
use Application_Error event handler in global.aspx
private void Application_Error(object sender, EventArgs e)
{
Exception ex = this.Server.GetLastError();
if(ex is HttpException && ex.InnerException != null && ex.InnerException is ViewStateException)
{
// log error
this.Server.ClearError();
// redirect to friendly error page
}
}
Tricking Event Handlers
web controls & event-based programming are important features
hides low-level protocol details
abstraction creates one big hidden form on the page which is submitted when a postback
occurs
two ways for form to be submitted
controls such as Button or ImageButton submit form when control is activated, generated
as standard input elements with a type attribute
controls that trigger postbacks cause form to be submitted by ASP.NET-generated
JavaScript
JavaScript function __doPostBack fills two hidden form fields, __EVENTTARGET &
__EVENTARGUMENT, with ID of the control that caused the postback & any event
parameters
__EVENTTARGET & __EVENTARGUMENT values are clearly visible in the HTTP body
event validation provides some level of protection against the modification of these
values
Event Validation Internals
when web control is rendered it may call RegisterForEventValidation method of ClientScriptManager
class
method computes hash code of client ID of the control and the hash code for the
event argument & then XORs the values
resulting hash added to internal ArrayList which is serialized to the hidden __EVENTVALIDATION
form field when page is rendered
serialization is performed by the ObjectStateFormatter class, protected with HMAC
when form is posted back to server the runtime calls the ValidateEvent method of
the ClientScriptManager
method decodes __EVENTVALIDATION field content & ensures postback was triggered
by a valid control with valid argument value
Hacking Event Validation
issues
implementation of controls is inconsistent, Button controls enables postback even
when disabled
__EVENTVALIDATION not bound to particular page state
in source code of AddAttributesToRender method of Button class RegisterForEventValidation
is called independently from value of Enabled property
__EVENTVALIDATION field contains valid reference for a disabled button
Protecting Against POST Attacks
programmatically unsubscribe to event handlers when a control is disabled (server-side)
defense in depth strategy uses multiple layers of counter-measures