I am looking into Stored Cross-site Scripting vulnerabilities that occur when the data provided by an attacker is saved on the server, and is then displayed upon subsequent requests without proper HTML escaping.
I have NET 5 ASP.NET Core application using MVC. The application is using jQuery and Telerik's ASP.NET Core library. Both use JSON data returned from the server.
The application has several action methods that query stored data in the database and return as JsonResult.
For example, the following action method
[HttpGet]
[Route("items/json/{id}")]
public async Task<ActionResult> GetName([FromRoute] int id)
{
var i = await _itemService.GetWorkItem(id);
return Json(new
{
ItemName = i.Name
});
}
and client side script shows the ItemName in html using jQuery
$.get(url)
.done(function (response, textStatus, jqXHR) {
$("#itemname").html(response);
})
Suppose a user has stored the name as <script>alert('evil');</script> then the code above will execute the evil script on client side.
The application is using Newtonsoft as default serializer. By default the response does not get Html encoded. The response from the server looks like
{"ItemName":"\u003Cscript\u003Ealert(\u0027evil\u0027);\u003C/script\u003E"}
Also setting default JsonSerializerSettings in Startup like below does not work the same way as the Html Encode.
var serializerSettings = new JsonSerializerSettings()
{
StringEscapeHandling = StringEscapeHandling.EscapeHtml
};
Is there any default way in ASP.NET Core (Net 5) to handle html encoding during JSON serialization?
I understand that there is WebUtility.HtmlEncode() and also HtmlEncoder class available which can be used to apply encoding selectively . I am looking for a solution to handle html encoding by default during the JSON serialization.
Is new System.Text.Json by default applies html encoding on property values?
UPDATE 1
The comments below suggest to configure NewtonsoftJson in startup.cs. Note that question is NOT how to configure newtonsoft globally but how to html encode property value during the serialization so client (Browser) wont execute the malicious script.
I have tried Newtonsoft.Json.StringEscapeHandling.EscapeHtml which did not work. The script still executes
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddNewtonsoftJson((options) =>
{
options.SerializerSettings.StringEscapeHandling = Newtonsoft.Json.StringEscapeHandling.EscapeHtml;
});
}
You have to use Newtonsoft.Json if you don't want to create tons of code for each quite simple case. This is working for me
[HttpGet]
public async Task<ActionResult> MyTest ()
{
return new JsonResult(new
{
ItemName = "<script> alert('evil');</script>"
});
}
and use response.itemName on client side
$("#itemname").html(response.itemName);
to use Newtonsoft.Json change your startup code to this
using Newtonsoft.Json.Serialization;
services.AddControllersWithViews()
.AddNewtonsoftJson(options =>
options.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver());
I have an application that uses Swashbuckle.AspNetCore" Version="5.6.3".
The implementation in startup is straight forward, the services inject the generator:
services.AddSwaggerGen();
And the app builder activates swagger in standard manner:
app.UseSwagger();
Why is it then that I find "application/*+json" as content type for certain post operations in the definition?. This is an issue as it breaks certain services that re-use the openapi definition.
Is there any known way of avoiding this and using plain "application/json" content type in the definition?.
Thanks,
If we just want use plain application/json content type, you can add attribute like : [Consumes( MediaTypeNames.Application.Json )]. If we want to use other plain, we can click the drop-down box.
Sample code:
[HttpGet]
[Consumes(MediaTypeNames.Application.Json )]
public async Task<IActionResult> Post()
{
}
How do I pass optional (nullable) [FromBody] parameter in ASP.NET Core (5.0)? If I don't send body in my request I get 415 Unsupported Media Type error. Can this be configured and if so, how to do it on a controller or action, rather than an app level? I presume it has to do something with model validation, but not sure. Thanks.
[HttpGet("[action]")]
public async Task<IActionResult> GetElementsAsync([FromBody] IEnumerable<int> elements = default)
{
var result = await dataService.GetData(elements);
return Ok(result);
}
EDIT: To clarify:
This is typical scenario and it works normally:
But passing empty body is returning 415 right away without even reaching action:
You can find a solution here:
https://github.com/pranavkm/OptionalBodyBinding
From this issue on github:
https://github.com/dotnet/aspnetcore/issues/6878
And from .net Core 5 you can use this one:
public async Task<IActionResult> GetElementsAsync([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] IEnumerable<int> elements = default)
...
Also needed (from Pawel experience):
services.AddControllers(options =>{options.AllowEmptyInputInBodyModelBinding = true;})
Just add content-type in your request header. Without the content-type:application/json will appear 415 when body is empty.
No changes to your controller. Test in my side is ok.
I created a new asp.net core 5 api project and this is my controller:
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace WebApi_net5.Controllers
{
public class HomeController : ControllerBase
{
[HttpGet("[action]")]
public string GetElementsAsync([FromBody] IEnumerable<int> elements = default)
{
return "value";
}
}
}
With ASP.NET Core 3.1, I could allow nullable optional parameters by implementing Nicola's suggestion:
services.AddControllers(options =>{options.AllowEmptyInputInBodyModelBinding = true;})
I will address some points that were not mentioned here.
To get rid of 415 without sending Content-Type you need to create your custom consumer of the Content-Type
https://stackoverflow.com/a/65813534/2531209
But I would say this is an overkill
If you pass Content-Type: application/json in your request header you will get "Body cannot be empty" (Tested on .NET 6) and only then #Nicola answer comes in handy.
From my tests it looks like modifications to the controller are not needed and only FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow) is enough with nullable type as parameter.
Or you can change nothing in your current code and send a header Content-Type: application/json with a body of {}. This will bypasses all of those errors, but this is not the most optimal solutions for public API's
In asp.net core web I create a controller and I can use:
return Json(new {status=true});
but in asp.net core web API I can't do it.
In a controller:
[HttpGet("{id}")]
public JsonResult Get(int id)
{
}
I can not return Json()
How to use it?
Asp.Net Core Web API does provide support for wide varieties of response types, with Json being one among them. You can do that like shown below. Make sure you have all your required dependencies. You can learn about the dependencies from the documentation link I attached in this answer.
[HttpGet]
public IActionResult Get()
{
return Json(model);
}
You can also specify strict response formats using the [Produces] Filter on your controller.
Configuring Custom Formatters
You can also configure your own custom formatters in Asp.Net Web API project by calling the .AddFormatterMappings() from ConfigureServices method inside of your Startup.cs. This allows for a greater control on your content negotiation part and lets you achieve strict restrictions.
Please go through this documentation to understand further.
Using Responses with Status Codes
However, when using Web API, I suggest you use the helper methods that are built in so that your response becomes more expressive as it contains both the response content along with the status code. An example of how to do that is below
[HttpGet]
public ActionResult Get()
{
return Ok(_authors.List());
}
For a full list of helper methods available, you can take a look at the Controller.cs and ControllerBase.cs classes.
Asp.net core web api inherit from controllerBase, which doesn't contain a Json(Object) method. You should initialize a new JsonResult yourself in the action.
[HttpGet("{id}")]
public JsonResult Get(int id)
{
return new JsonResult(new { status = true });
}
I have a .NET Core 2.0 application, using Swashbuckle/Swagger to generate API documentation. When we were on 2.1.0-preview, Swagger was working fine. Then we did the big upgrade to 2.1.0 release and SDK 2.1.300. We didn't notice exactly when things broke, but now our Swagger docs won't load. Here's what we see:
Project has a reference to Swashbuckle.AspNetCore version 2.5.0. The relevant code in Startup.cs is below. In ConfigureServices():
services.AddSwaggerGen(swaggerOptions =>
{
// Register a swagger doc
swaggerOptions.SwaggerDoc("v1", new Info
{
// Optional descriptive info that will be included in the Swagger output
Contact = new Contact
{
Name = "LightSail",
Url = "https://myurl.com/"
},
Description = "A description of the API can go here",
Title = "My API",
Version = "v1"
});
// Xml file to get comment information from
swaggerOptions.IncludeXmlComments("App_Data/Api.xml");
});
And in Configure():
app.UseSwagger();
app.UseSwaggerUI(swaggerUiOptions => swaggerUiOptions.SwaggerEndpoint("/swagger/v1/swagger.json", "My API v1"));
I found lots of other similar questions, one of which suggested that there might be duplicate endpoints; I tried adding a call to .ResolveConflictingEndpoints() but that made no difference. I have searched through my project folders and there is no file called swagger.json, so I'm guessing that's the problem.
Any ideas why this is not working, or how to fix?
This is usually indicative of controllers/actions that Swashbuckle doesn't support for one reason or another.
It's expected that you don't have a swagger.json file in your project. Swashbuckle creates and serves that dynamically using ASP.NET Core's ApiExplorer APIs. What's probably happening here is that Swashbuckle is unable to generate Swagger.json and, therefore, the UI is failing to display.
As HelderSepu said, it's hard to know exactly what caused the failure, so the best way to debug is probably just to remove half your controllers (just move the files to a temporary location) and check whether the issues persists. Then you'll know which half of your controllers contains the troublesome action. You can 'binary search' removing controllers (and then actions) until you figure out which action method is causing Swashbuckle to not be able to generate Swagger.json. Once you know that, it should be obvious whether this is some issue in your code or an issue that should be filed in the Swashbuckle repo.
For example, Swashbuckle appears to not support open generics, so having a response type attribute like [ResponseType(typeof(IEnumerable<>))] could cause this sort of behavior. It could also be an issue with ambiguous routes or something like that tripping Swashbuckle up. Once you've narrowed down the cause of failure to something more specific like that, it can either be fixed or filed, as appropriate.
Today I found out that I could just go to the json url in the browser and get some error information
for example
myapiurl/api/vi/swagger.json
I was able to solve this error by explicitly adding the http verb attribute to my asp.net core 2.x controller method. The convention of prefixing the method name with the http verb is not enough for Swashbuckle apparently.
[HttpPost]
public async Task<IActionResult> AddNewData([FromBody] MyType myType) { … }
In my case I can reproduce your error by omitting "." from the end point as you have done.
I don't get the error if I include "." at the start of the path.
Here is more of my code in case it is relevant.
In ConfigureServices I have
services.AddSwaggerGen(c =>
{
c.OperationFilter<AuthorizationHeaderParameterOperationFilter>();
c.SwaggerDoc("v1", new Info
{
Version = "v1",
Title = "My API",
Description = "ASP.NET Core Web API",
TermsOfService = "None",
Contact = new Contact
{
Name = "my name",
Email = "me#myemail.com"
}
});
});
In configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRewriter(new RewriteOptions()
.AddRedirectToHttpsPermanent());
app.UseSwagger(c =>
{
c.RouteTemplate =
"api-docs/{documentName}/swagger.json";
});
app.UseSwaggerUI(c =>
{
//Include virtual directory if site is configured so
c.RoutePrefix = "api-docs";
c.SwaggerEndpoint("./v1/swagger.json", "Api v1");
});
app.UseMvc(routes =>
{
routes.MapRoute(
"default",
"{controller=Home}/{action=Index}/{id?}");
});
Also there is
public class AuthorizationHeaderParameterOperationFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
var filterPipeline = context.ApiDescription.ActionDescriptor.FilterDescriptors;
var isAuthorized = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is AuthorizeFilter);
var allowAnonymous = filterPipeline.Select(filterInfo => filterInfo.Filter).Any(filter => filter is IAllowAnonymousFilter);
if (isAuthorized && !allowAnonymous)
{
if (operation.Parameters == null)
operation.Parameters = new List<IParameter>();
operation.Parameters.Add(new NonBodyParameter
{
Name = "Authorization",
In = "header",
Description = "access token",
Required = true,
Type = "string"
});
}
}
My dependencies are
Microsoft.AspNetCore.App (2.1.0)
Swashbuckle.AspNetCore (2.5.0)
Microsoft.NETCore.App (2.1.0)
Personally I was a bit quick and forgot to add this line to the method ConfigureServices in Startup.cs.
services.AddSwaggerDocument();
In my case, I missed the 'HttpAttribute':
public async Task<IEnumerable<ClientesListDto>> GetAll()
{
return await _service.GetAllAsync();
}
Then I put it and swagger likes it:
[HttpGet]
public async Task<IEnumerable<ClientesListDto>> GetAll()
{
return await _service.GetAllAsync();
}
In my case, I had this:
[HttpGet("CleanUpSnoozedLeads")]
public async Task<ActionResult<bool>> CleanUpSnoozedLeads()
[HttpGet("CleanUpSnoozedLeads")]
public async Task<ActionResult<bool>> DoSomethingElse()
Notice the HttpGet() had the same name. That causes the undefined error as well.
A very common case is ambiguity. Just use the same signature for two PUT or POST operations for example and you will get the error.
Others answers did not worked for me.
I was able to fix and understand my issue when I tried to go to the swagger.json URL location:
https://localhost:XXXXX/swagger/v1/swagger.json
The page will show the error and reason why it is not found.
In my case, I saw that there was a misconfigured XML definition of one of my methods based on the error it returned:
NotSupportedException: HTTP method "GET" & path "api/Values/{id}" overloaded by actions - ...
...
...
In my case, i just forgot to add the HttpPostAttribute annotation to the method.
[HttpPost]
public ActionResult Post()
{
return Ok();
}
In my case there was a conflict in the schemaId. Apparently every class in the swagger JSON must have a unique schemaId. If you have two classes in different namespaces with the same name this will not work. We have to configure "UseFullTypeNameInSchemaIds" in the startup class.
Add "options.CustomSchemaIds(x => x.FullName);" in "services.AddSwaggerGen"
I found the trace by enabling Output window in VS, selecting the main project from Show output from dropdown list then visit http://{yourapiendpoint}/swagger/v1/swagger.json
If your api have same two or more [HttpGet] its not working swagger.
You should be specify [HttpGet] , [HttpGet ("{id}")]
simple solution