Redacting Log Content - audit.net

In Audit.Net, is it possible to filter request bodies being saved containing sensitive data? This is for Audit.WebAPI.
E.g., There's a JSON request body with {"username": "me", "password": "sensitive"}. Can the password value "sensitive" be replaced by ""?

You could add a custom action that sanitizes the body string on the audit event.
For example:
using Audit.WebApi;
Audit.Core.Configuration.AddCustomAction(ActionType.OnScopeCreated, scope =>
{
var action = scope.GetWebApiAuditAction();
var bodyString = action?.RequestBody?.Value?.ToString();
if (!string.IsNullOrEmpty(bodyString))
{
action.RequestBody.Value = Sanitize(bodyString);
}
});
Using a regular expression:
private string Sanitize(string input)
{
var pattern = #"\s*\""password\"" *: *\"".*\""(,|(?=\s+\}))";
var substitution = #"""password"": """"";
var regex = new Regex(pattern);
return regex.Replace(input, substitution);
}

Related

Set Integer header value in asp.net MVC

I want to set an Int Header value for terminalId.
Currently HttpClient header only takes <String, String> pairs. How can I add a header with <String, int>?
This Web service accept Int for terminalId.
Following Code :
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:8678");
var postview = new CitizenResult
{
cpartyUsername = "abc",
cpartyPassword = "123",
};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("token", "d448f575db31a44e32e0a17f24bc947cdd4edb4a7db8729734c81b9e139286af36dd9c5e42793d494e0f7bd45b25e5f3b77472949f38bcdb3dfa2b8d1ef376848ec6ce65b9a7d7be343c5c1b59089082");
client.DefaultRequestHeaders.Add("clientIPAddress", "178.173.147.4");
client.DefaultRequestHeaders.Add("clientAgentInfo", "192.168.0.227");
client.DefaultRequestHeaders.Add("terminalId", 123);
int a = 190766;
var postTask = client.PostAsJsonAsync<CitizenResult>("http://test", postview);
postTask.Wait();
var result = postTask.Result;
if (result.IsSuccessStatusCode)
{
var x = result.Content.ReadAsAsync<SalamtResultViewModel>();
ViewBag.x = x;
}
else
{
var x = result.Content.ReadAsAsync<badRequestResult>();
ViewBag.x = x;
}
}
When you add the integer just call the .toString() method.
client.DefaultRequestHeaders.Add("terminalId", 123.toString());
or
client.DefaultRequestHeaders.Add("terminalId", a.toString());
This is because the header can be considered as just a JSON object where everything is stored in quotes like this:
{
"key": "value"
}
so your entry would be stored like this
"terminalId": "123"
JSON can hold information like:
"terminalId": 123
but there are no overloads available for that.

Response on created context keeps giving me NullStream

I'm trying to write a middleware for batch requests i .net core 2.0.
So far the I have splitted the request, pipe each request on to the controllers.
The controllers return value, but for some reason the response on the created context that I parse to the controllers keeps giving me a NullStream in the body, so I think that there is something that I miss in my setup.
The code looks like this:
var json = await streamHelper.StreamToJson(context.Request.Body);
var requests = JsonConvert.DeserializeObject<IEnumerable<RequestModel>>(json);
var responseBody = new List<ResponseModel>();
foreach (var request in requests)
{
var newRequest = new HttpRequestFeature
{
Body = request.Body != null ? new MemoryStream(Encoding.ASCII.GetBytes(request.Body)) : null,
Headers = context.Request.Headers,
Method = request.Method,
Path = request.RelativeUrl,
PathBase = string.Empty,
Protocol = context.Request.Protocol,
Scheme = context.Request.Scheme,
QueryString = context.Request.QueryString.Value
};
var newRespone = new HttpResponseFeature();
var requestLifetimeFeature = new HttpRequestLifetimeFeature();
var features = CreateDefaultFeatures(context.Features);
features.Set<IHttpRequestFeature>(newRequest);
features.Set<IHttpResponseFeature>(newRespone);
features.Set<IHttpRequestLifetimeFeature>(requestLifetimeFeature);
var innerContext = _factory.Create(features);
await _next(innerContext);
var responseJson = await streamHelper.StreamToJson(innerContext.Response.Body);
I'm not sure what it is I'm missing in the setup, since innerContext.Response.Body isn't set.
One of the endpoints that I use for testing and that gets hit looks like this
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
I found the error, or two errors for it to work.
First I had to change my newResponse to
var newRespone = new HttpResponseFeature{ Body = new MemoryStream() };
Since HttpResponseFeature sets Body to Stream.Null in the constructor.
When that was done, then Body kept giving an empty string when trying to read it. That was fixed by setting the Position to Zero like
innerContext.Response.Body.Position = 0;

How to get file size in MultipartStreamProvider GetStream when missing from ContentDisposition

I creating a custom MultipartStreamProvider to store files in Azure File Storage as part of a Lift & Shift effort of a legacy application. The client is using AngularJS on the front end and WebAPI on the backend. When I am trying to use the MultipartStreamProvider, I need to implement GetStream to return a stream for it to write to. I am using cloudFile.OpenWrite which asks for the size of the stream/file that will be written to it. However, in GetStream, the ContentDisposition.Size is empty. Is there a way I can either make the AngularJS send the content size for each file or on the backend, maybe I can dig the size of the file stream from somewhere else? Any help would be greatly appreciated. Thanks!
MultipartStreamProvider
public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
{
// For form data, Content-Disposition header is a requirement
ContentDispositionHeaderValue contentDisposition = headers.ContentDisposition;
Console.WriteLine(files.Count);
if (contentDisposition != null)
{
// create default filename if its missing
contentDisposition.FileName = (String.IsNullOrEmpty(contentDisposition.FileName) ? $"{Guid.NewGuid()}.data" : contentDisposition.FileName);
// We won't post process files as form data
_isFormData.Add(false);
CloudMultipartFileData fileData = new CloudMultipartFileData(headers, _fileRepository.BaseUrl, contentDisposition.FileName);// your - aws - filelocation - url - maybe);
_fileData.Add(fileData);
var azureStream = _fileRepository.GetWriteStream(contentDisposition.Size, _relativeDirectory, fileData.FileName);
return azureStream;
// We will post process this as form data
_isFormData.Add(true);
}
throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part..");
}
Actual Call to Azure
public override Stream GetWriteStream(long? fileSize, string relativeDirectory, string filename)
{
var combinedRelativeDirectory = GetCloudDirectory(relativeDirectory);
CloudFile cloudFile = combinedRelativeDirectory.GetFileReference(filename);
return cloudFile.OpenWrite(fileSize, null, null);
}
AngularJS File Upload Code
/********************************** Add/Upload Photos **************************************/
$scope.$watch('files', function (files) {
$scope.formUpload = false;
console.log(files);
if (files != null) {
for (var i = 0; i < files.length; i++) {
$scope.errorMsg = null;
(function (file) {
upload(file);
})(files[i]);
}
}
});
function upload(file) {
file.upload = Upload.upload({
url: window.location.origin + "/api/mydocs/uploadfile?storeFolder=" + $scope.attachmentFolder + "&storeId=" + $scope.storeId + "&userId=" + $scope.currentUser.UserId,
method: 'POST',
headers: {},
fields: {},
file: file
});
I wound up manually adding the file size to the header
function upload(file) {
file.upload = Upload.upload({
url: window.location.origin + "/api/mydocs/uploadfile?storeFolder=" + $scope.myFolder + "&clientId=" + $scope.clientId,
method: 'POST',
headers: { 'file-info':file.name + "-/" + file.size },
fields: {},
file: file
});
And then in the constructor, I use that to create a lookup table:
public MyCloudMultipartFormDataStreamProvider(string relativeDirectory, IEnumerable<string> lookupInfo)
{
NewFileNames = new Dictionary<string, string>();
_fileRepository = new CloudFileRepository();
_relativeDirectory = relativeDirectory;
_uploadedFilesLookup = new Dictionary<string, long>();
foreach (var fileInfo in lookupInfo)
{
var values = Regex.Split(fileInfo, #"-/");
_uploadedFilesLookup.Add(values[0], Int64.Parse(values[1]));
}
}
Then grab the file's size out of the lookup table and pass that to my GetWriteStream method
var azureStream = _fileFacade.GetWriteStream(_uploadedFilesLookup[fileName],
_relativeDirectory, fileData.FileName, out newFileName);

How to get all url parameters in asp.net mvc?

My test url:
localhost:61578/?type=promotion&foo=bar
I usually use this way to get the value of type parameter:
public IActionResult Index(string type)
{
// type = "promotion"
}
My question: How to detect all parameters in the url? I want to prevent to access the page with some unknown parameter.
Something like this:
public IActionResult Index(string type, string foo)
{
if (!string.IsNullOrEmpty(foo))
{
return BadRequest(); // page 404
}
}
The problem is: I don't know exactly the name what user enters. So, it can be:
localhost:61578/?bar=baz&type=promotion
You can use the HttpContext Type to grab the query string
var context = HttpContext.Current;
then, you can grab the entire query string:
var queryString = context.Request.QueryString
// "bar=baz&type=promotion"
or, you can get a list of objects:
var query = context.Request.Query.Select(_ => new
{
Key = _.Key.ToString(),
Value = _.Value.ToString()
});
// { Key = "bar", Value = "baz" }
// { Key = "type", Value = "promotion" }
or, you could make a dictionary:
Dictionary<string, string>queryKvp = context.Request.GetQueryNameValuePairs()
.ToDictionary(_=> _.Key, _=> _.Value, StringComparer.OrdinalIgnoreCase);
// queryKvp["bar"] = "baz"
// queryKvp["type"] = "promotion"

Is there a utility to serialise an object as HTTP content type "application/x-www-form-urlencoded"?

I've never had to do this before, because it's always only been an actual form that I've posted as that content type, but recently I had to post three variables like that, and I resorted to a sordid concatenation with & and =:
var content = new StringContent("grant_type=password&username=" + username + "&password=" + password.ToClearString(), Encoding.UTF8,
"application/x-www-form-urlencoded");
I'm sure there must be a utility method that would do that, and do it better, with any necessary encoding. What would that be?
If this is a POCO and just using the Newtonsoft library, you can use this as well:
public static class FormUrlEncodedContentExtension
{
public static FormUrlEncodedContent ToFormUrlEncodedContent(this object obj)
{
var json = Newtonsoft.Json.JsonConvert.SerializeObject(obj);
var keyValues = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
var content = new FormUrlEncodedContent(keyValues);
return content;
}
}
And a sample usage would be:
var myObject = new MyObject {Grant_Type = "TypeA", Username = "Hello", Password = "World"};
var request = new HttpRequestMessage(HttpMethod.Post, "/path/to/post/to")
{
Content = myObject.ToFormUrlEncodedContent()
};
var client = new HttpClient {BaseAddress = new Uri("http://www.mywebsite.com")};
var response = await client.SendAsync(request);
Use reflection to get the property names and values and then use them to create a System.Net.Http.FormUrlEncodedContent
public static class FormUrlEncodedContentExtension {
public static FormUrlEncodedContent ToFormUrlEncodedContent(this object obj) {
var nameValueCollection = obj.GetType()
.GetProperties()
.ToDictionary(p => p.Name, p => (p.GetValue(obj) ?? "").ToString());
var content = new FormUrlEncodedContent(nameValueCollection);
return content;
}
}
From there it is a simple matter of calling the extension method on an object to convert it to a FormUrlEncodedContent
var model = new MyModel {
grant_type = "...",
username = "...",
password = "..."
};
var content = model.ToFormUrlEncodedContent();
You should be able to use string interpolation for that. Something like:
var content = new StringContent($"grant_type=password&username={username}&password={password}", Encoding.UTF8, "application/x-www-form-urlencoded");
Or wrap this inside a helper/factory method:
public static class StringContentFactory
{
public static StringContent Build(string username, string password)
{
return new StringContent($"grant_type=password&username={username}&password={password}", Encoding.UTF8, "application/x-www-form-urlencoded");
}
}

Resources