Web vulnerabilities and options for .net core API 3.1
Tags: security, dotnet, webapi, jwt
Published: 2020-04-08 12:04:22

There are four common vulnerabilities in web applications. Be aware of these risks, master features of the technology stacks that help you secure your apps and prevent security breaches is necessary.

1. Cross-site scripting attacks (XSS). Core tip: All data received from clients are untrusted. When you want to output the content, keep an eye on any possibility of including any executable scripts.
https://en.wikipedia.org/wiki/Cross-site_scripting
https://owasp.org/www-community/attacks/xss/
https://docs.microsoft.com/en-us/aspnet/core/security/cross-site-scripting?view=aspnetcore-3.1

2. SQL injection attacks. Core tip: The concatenation of raw SQL command text with parameters or parts from an untrusted source should be seriously validated.
https://en.wikipedia.org/wiki/SQL_injection
https://portswigger.net/web-security/sql-injection
https://docs.microsoft.com/en-us/ef/core/querying/raw-sql

3. Cross-Site Request Forgery (CSRF), also known as one-click attack or session riding. Core tip: Two websites are browsed, one log in and another is malicious. Submit requests from the malicious website attached with your valid cookie authentication is the common way to attack.
https://en.wikipedia.org/wiki/Cross-site_request_forgery
https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-3.1

4. Open redirect attacks, also known as "Unvalidated Redirects and Forwards". Core tip: If the login redirects with an unchecked query parameter, users from a fake link could be redirected to a similar login page and causing their credential data leaks.
https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/understanding-and-discovering-open-redirect-vulnerabilities/
https://docs.microsoft.com/en-us/aspnet/core/security/preventing-open-redirects?view=aspnetcore-3.1

.Net core web API 3.1 is the latest framework of Microsoft to develop REST API.
Use cookie authentication without ASP.NET Core Identity (https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1.) is easy and quick. HttpOnly cookies will be used by default. Httponly flag is very important to avoid any XSS attack and has other benefits (https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies). Cookie solution relies on client's cookie support.

Another common authentication solution for API is to use JWT (JSON Web Token, https://jwt.io). https://jasonwatmore.com/post/2019/10/11/aspnet-core-3-jwt-authentication-tutorial-with-example-api. In start.cs:
```
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            if (Settings.Cors?.Length > 0)
            {
                services.AddCors(options =>
                {
                    options.AddPolicy("platform",
                    builder =>
                    {
                        builder.WithOrigins(Settings.Cors)
                            // Support https://*.domain.com
                            .SetIsOriginAllowedToAllowWildcardSubdomains()

                            // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
                            // https://stackoverflow.com/questions/24687313/what-exactly-does-the-access-control-allow-credentials-header-do
                            // JWT is not a cookie solution, disable it without allow credential
                            // .AllowCredentials()
                            .DisallowCredentials()

                            // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
                            // Without it will popup error: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response
                            .AllowAnyHeader()

                            // Web Verbs like GET, POST, default enabled
                            .AllowAnyMethod();
                    });
                });
            }

            // Configue compression
            // https://gunnarpeipman.com/aspnet-core-compress-gzip-brotli-content-encoding/
            services.Configure<BrotliCompressionProviderOptions>(options =>
            {
                options.Level = CompressionLevel.Optimal;
            });

            services.AddResponseCompression(options =>
            {
                options.EnableForHttps = true;
                options.Providers.Add<BrotliCompressionProvider>();
            });

            // Configue JWT authentication
            // https://jwt.io/
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    // Is SSL only
                    options.RequireHttpsMetadata = Settings.SSL;

                    // Save token, True means tokens are cached in the server for validation
                    options.SaveToken = false;

                    // Token validation parameters
                    options.TokenValidationParameters = new TokenValidationParameters()
                    {
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(app.Configuration.SymmetricKey)),
                        ValidateIssuer = false,
                        ValidateAudience = false
                    };
                });
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            // Enable HTTPS redirect
            if (Settings.SSL)
                app.UseHttpsRedirection();

            app.UseRouting();

            // Enable CORS (Cross-Origin Requests)
            // The call to UseCors must be placed after UseRouting, but before UseAuthorization
            if (Settings.Cors?.Length > 0)
            {
                app.UseCors("platform");
            }

            app.UseAuthentication();
            app.UseAuthorization();

            // Enable compression
            app.UseResponseCompression();

            app.UseEndpoints(endpoints =>
            {
                // Apply authentication by default
                endpoints.MapControllers().RequireAuthorization();
            });
        }

After the user log in successfully, add the codes below to generate the token:
        /// <summary>
        /// Login for authentication
        /// 登录授权
        /// </summary>
        /// <param name="model">Data model</param>
        /// <returns>Result</returns>
        [AllowAnonymous]
        [HttpPost("Login")]
        public async Task Login([FromBody]LoginModel model)
        {
            // Act
            var result = await Service.LoginAsync(model);

            if (result.OK)
            {
                // Logined user id
                var userId = result.Data.Get("token_user_id", 0);

                // User role
                var role = result.Data.Get("role", UserRole.User);

                // Token handler
                var tokenHandler = new JwtSecurityTokenHandler();

                // Key bytes
                var key = Encoding.ASCII.GetBytes(App.Configuration.SymmetricKey);

                // Token descriptor
                var tokenDescriptor = new SecurityTokenDescriptor
                {
                    Subject = new ClaimsIdentity(new Claim[]
                    {
                        new Claim(ClaimTypes.Name, userId.ToString()),
                        new Claim(ClaimTypes.Role, role.ToString().ToLower()),
                    }),
                    // Suggest to refresh it at 5 minutes interval, two times to update
                    Expires = DateTime.UtcNow.AddMinutes(12),
                    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256)
                };

                // Hold the token value and then return to client
                var token = tokenHandler.CreateToken(tokenDescriptor);
                result.Data["authorization"] = tokenHandler.WriteToken(token);
            }

            // Output
            await ResultContentAsync(result);
        }
```
Because the token is stateless, Web APIs are always facing a replay attack, also known as playback attack. bearer.SaveToken = true means you could access it through await HttpContext.GetTokenAsync("access_token") for any outgoing request. Add necessary validation logic in the database side is helpful. A short-lived, strict authentication with rate-limiting policy token solution will make the project much stronger.