Base solution for your next web application
Open Closed

Session Hijacking #11883


User avatar
0
hongbing.wang created

Hi @ismcagdas,

What is your product version? v12.4.0 What is your product type (Angular or MVC)? Angular What is product framework type (.net framework or .net core)? .net 7

The Session Hijacking attack compromises the session token by stealing or predicting a valid session token to gain unauthorized access to the Web Server. Using the Cookie, attacker can gain access to the application as authorized user and can modify the data, and he can pose as legitimate users, gain information, and take actions under the assumed identity. Please see the details in the following screenshots.

When copying an admin's Abp.AuthToken cookie to a non-admin user, WMS Pro shows an error message: "Request data failed, Session.UserId is null! Probably, user is not logged in.". Reload the page, the user is logged out. This is normal. Please investigate why sometimes the user is not logged out. Refresh the page, the non-admin user is shown as an admin.

Related:

  1. My question at https://support.aspnetzero.com/QA/Questions/10654/Login-Issue-w-Antiforgery-Tokens

  2. Why do we have to comment out [ValidateAntiForgeryToken] in the following places: \webapi\src\umsplus.Web.Core\Controllers\TenantCustomizationController.cs //[ValidateAntiForgeryToken] public async Task<JsonResult> UploadLightLogo() ... //[ValidateAntiForgeryToken] public async Task<JsonResult> UploadCustomCss() ... \webapi\src\umsplus.Web.Core\Controllers\UsersControllerBase.cs //[ValidateAntiForgeryToken] public async Task<JsonResult> ImportFromExcel() ... ...

  3. \webapi\src\umsplus.Web.Host\Startup\Startup.cs services.AddControllersWithViews(options => { options.Filters.Add(new AbpAutoValidateAntiforgeryTokenAttribute());

It should be done for dynamic web api automatically. Does this mean, with this set, we don't need to set the attribute [ValidateAntiForgeryToken] in the places?


14 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    You shouldn't disable ValidateAntiForgeryToken attribute. Did you face any issues while using it ? If not, you can enable it back. It seems like your HTTPS certificate is not valid as far as I can see from the screnshot. I suggest using a valid certificate. This should solve the problem.

  • User Avatar
    0
    hongbing.wang created

    I have reinstated [ValidateAntiForgeryToken] attributes. But it didn't help solve the reported session hijacking described at the top pf this post.

    The example of session hijacking: I copied the AbP.AuthToken from an admin user to a non-admin user. The non-admin user now has the admin rights, which is an authentication and authorization issue.

    I think the [ValidateAntiForgeryToken] attribute won't directly address the session hijacking. It is not directly applicable to preventing token impersonation. Is this an authentication issue?

    Please note that this issue can also be reproduced with Zero app 12.4.2.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Are you hosting Angular app with the Host app together under the same domain ?

  • User Avatar
    0
    hongbing.wang created

    Hi @ismcagdas, Yes, we host Angular app with the Host app together under the same domain.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Thanks,

    In that case, instead of directly serving Angular app (and its index.html file), you can host this file via an ASP.NET Core Controller and cshtml file. You can follow an approach like this https://github.com/angular/angular-cli/issues/2960#issuecomment-263639803

    In that case, Anti-Forgery will work.

    I assume you already enabled HTTP-Only cookies. In that case, client side can't access auth token at all.

  • User Avatar
    0
    hongbing.wang created

    Hi @ismcagdas,

    Yes, our app has HTTP-Only cookies enabled. The HttpOnly flag prevents client-side scripts (JavaScript) from accessing the cookie containing the token. This helps mitigate Cross-site Scripting (XSS) attacks, where an attacker injects malicious code into a web page and steals the token using JavaScript.

    However, it doesn't prevent access from the developer tools provided by your browser. Anyone with access to your browser, including yourself, can view and copy the token using these tools.

    Please try Zero app 12.4.2: copy the Abp.AuthToken from an admin user to a non-admin user. The non-admin user now has the admin rights.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    In that case, as I stated above (if you also want to enable Anti-Forgery validation), you need to follow;

    In that case, instead of directly serving Angular app (and its index.html file), you can host this file via an ASP.NET Core Controller and cshtml file. You can follow an approach like this https://github.com/angular/angular-cli/issues/2960#issuecomment-263639803

    This way, you can force anti-forgery validaiton for your Angular client.

  • User Avatar
    0
    hongbing.wang created

    Hi @ismcagdas, Not sure whether forcing anti-forgery validation will fix the session hijacking issue. Put anti-forgery validation aside, have you considered the session hijacking issue in the original Zero app. Please note that I can reproduce it in ASP.NET Zero 12.4.2.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Yes, we did but it didn't come out in our tests. Please take a look at the following artice https://www.globalsign.com/en/blog/session-hijacking-and-how-to-prevent-it.

    Normally tools like you are using offers a solution to their findings. Do you have such an information ? We can evaluate to implement it.

  • User Avatar
    0
    hongbing.wang created

    Hi @ismcagdas,

    I've added SessionValidationMiddleware, seesionID claim in access and refresh tokens, and UserSessionCache with seesionID and userId. See the code below.

    If an admin's token is copied to a non-admin user. jwtUserIdClaimed is the admin userId, but userId is also changed to admin userId / jwtUserIdClaimed?

    Does this mean that the userId in _abpSession is always the same as jwtUserIdClaimed. Is the principal synced with the access token by ABP?

    Please reproduce this session hijacking issue with ASP.NET Zero 13.1.0 code. We use ASP.NET Zero 13.1.0.

    This is a critical security issue. A hacker can copy an admin user's access token through developer tool and make him gain admin privileges. Please investigate and resolve the issue.

    ` public class SessionValidationMiddleware { private readonly RequestDelegate _next; private readonly ICacheManager _cacheManager; private readonly IAbpSession _abpSession;

        public SessionValidationMiddleware(RequestDelegate next, ICacheManager cacheManager, IAbpSession abpSession)
        {
            _next = next;
            _cacheManager = cacheManager;
            _abpSession = abpSession;
        }
    
        public async Task Invoke(HttpContext context)
        {
            var userClaims = context.User.Claims;
            var sessionIdClaim = userClaims.FirstOrDefault(c => c.Type == "SessionId");
            var userIdClaim = userClaims.FirstOrDefault(c => c.Type == "user_identifier");
    
            if (sessionIdClaim != null && userIdClaim != null)
            {
                if (userIdClaim.Value != _abpSession.ToUserIdentifier().ToUserIdentifierString())
                {
                    //never reached here. They should be the same.
                    throw new WmsproException((int)HttpStatusCode.Unauthorized, "Invalid user identifier");
                }
    
                //user_identifier: UserId + "@" + TenantId
                var parts = userIdClaim.Value.Split('@');
                if (parts.Length == 2)
                {
                    var userId = parts[0];
                    //var tenantId = parts[1];
    
                    if (userId != _abpSession.ToUserIdentifier().UserId.ToString())
                    {
                        //never reached here. They should be the same.
                        throw new WmsproException((int)HttpStatusCode.Unauthorized, "Invalid user identifier");
                    }
    
                    var sessionId = sessionIdClaim.Value;
    
                    var token = context.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
    
                    if (!string.IsNullOrEmpty(token))
                    {
                        var handler = new JwtSecurityTokenHandler();
                        var jwtToken = handler.ReadToken(token) as JwtSecurityToken;
    
                        if (jwtToken != null)
                        {
                            var jwtUserIdClaim = jwtToken.Claims.FirstOrDefault(claim => claim.Type == JwtRegisteredClaimNames.Sub);
                            var jwtSessionIdClaim = jwtToken.Claims.FirstOrDefault(claim => claim.Type == "SessionId");
    
                            if (jwtUserIdClaim != null && jwtSessionIdClaim != null)
                            {
                                var jwtUserIdClaimed = jwtUserIdClaim.Value;
                                //seems _abpSession always gets the userId from jwt access token rather than from the principal, or the principal is synced with the token by ABP. 
                                //var userId = _abpSession.UserId.Value.ToString();
                                var jwtSessionIdClaimed = jwtSessionIdClaim.Value;
    
                                // Retrieve the cached session ID using the correct method signature
                                var cache = _cacheManager.GetCache<string, string>("UserSessionCache");
                                var cachedSessionId = await cache.GetAsync(userId, async (key) => null); // Assuming a factory function returning null if not found
    
                                // Retrieve the current session user ID (assumes session is available)
                                //var sessionUserId = context.Session.GetString("UserId");
    
                                //If an admin's token is copied to a non-admin. jwtUserIdClaimed is the admin userId, but userId is also changed to admin userId / jwtUserIdClaimed?
                                //This means that the userId in _abpSession is always the same as jwtUserIdClaimed. Is the principal synced with the access token by ABP?
                                if ((cachedSessionId != null && (cachedSessionId != sessionId || cachedSessionId != jwtSessionIdClaimed)) ||
                                    (jwtSessionIdClaimed != sessionId || jwtUserIdClaimed != userId))
                                {
                                    throw new WmsproException((int)HttpStatusCode.Unauthorized, "Invalid session");
                                }
                            }
                            else
                            {
                                //context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                                //await context.Response.WriteAsync("Token is missing required claims.");
                                //return;
                            }
                        }
                    }
                }
            }
    
            await _next(context);
        }
    }`
    

    In Startup.cs, I have added app.UseMiddleware<SessionValidationMiddleware>();

    The changes in TokenAuthController.cs: InternalAuthenticate(): ` var sessionId = Guid.NewGuid().ToString(); await _cacheManager.GetCache("UserSessionCache").SetAsync(loginResult.User.Id.ToString(), sessionId, TimeSpan.FromMinutes(30));

            var refreshToken = CreateRefreshToken(await CreateJwtClaims(loginResult.Identity, loginResult.User,
                tokenType: TokenType.RefreshToken, sessionId: sessionId));
    
            var accessToken = CreateAccessToken(await CreateJwtClaims(loginResult.Identity, loginResult.User,
                refreshTokenKey: refreshToken.key, sessionId: sessionId));`
    

    ` private async Task<IEnumerable

            if (_identityOptions.ClaimsIdentity.UserIdClaimType != JwtRegisteredClaimNames.Sub)
            {
                claims.Add(new Claim(JwtRegisteredClaimNames.Sub, nameIdClaim.Value));
            }
    
            if (!string.IsNullOrEmpty(sessionId))
            {
                claims.AddRange(new[]
                {
                    new Claim("SessionId", sessionId),
                });
            }
    
            claims.AddRange(new[]
            {
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(),
                    ClaimValueTypes.Integer64),
                new Claim(AppConsts.TokenValidityKey, tokenValidityKey),
                new Claim(AppConsts.UserIdentifier, user.ToUserIdentifier().ToUserIdentifierString()),
                new Claim(AppConsts.TokenType, tokenType.To<int>().ToString())
            });     `       
    
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @hongbing.wang

    Does your implementation fix the problem ?

  • User Avatar
    0
    hongbing.wang created

    Hi @ismcagdas,

    It doesn't.

    So, I want to use ASP.NET Core Session instead of AbpSession.

    I aim to use HttpContext.Session.SetString("SId", sessionId); HttpContext.Session.SetString("UId", loginResult.User.Id.ToString());

    In Startup.cs, I added app.UseSession()

    But it throws an exception at app.UseAbp(options => { options.UseAbpRequestLocalization = false; //used below: UseAbpRequestLocalization }); The exception is Component Abp.Domain.Uow.UnitOfWorkDefaultOptions could not be registered. There is already a component with that name. Did you want to modify the existing component instead? If not, make sure you specify a unique name.

    How do I resolve the error? Can I use ASP.NET Core Session anyway?

  • User Avatar
    0
    hongbing.wang created

    I've figured out the issue with using use ASP.NET Core Session. In startup.cs, I also need to add the following service registration.

    services.AddSession(options => { options.IdleTimeout = TimeSpan.FromSeconds(30); options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; });

    When logging in, the HttpContext.Session has the two key-value pairs. However in Invoke(HttpContext context) in the validator, context.Session lost the key-value pairs. So it is still not working.

    Developer tool, Application tab > Under Storage, select Cookies and then the site (http://localhost:4200), there is not any cookie named .AspNetCore.Session or something similar.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Replied to your email. We can continue via email adn update the ticket when the issue is resolved.