Passing an image from a Wep Api service to another Web Api service - asp.net

For security reasons, I am building two Web Api services. The first Web Api app will have access to an image generating service, and will act as a security proxy. The second Web Api app will call the first app from the internet and retrieve the image.
However, I can't seem to get to negotiate passage of the image correctly. My thought was to have the security proxy Web API to get the image, and then pass it as a byte array my other service which would allow a user to download the image. However, when my browser attempts to open the image, it is always corrupted.
Here is the security proxy getting the image, which I know is successful:
public byte[] Get(string invoice, string Customer)
{
object image;
try
{
image = _repo.GetImage(invoice, Customer);
}
catch (ApplicationException exc)
{
var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent(string.Format("No Image with Invoice Number = {0}", invoice.ToString())),
ReasonPhrase = "Image Not Found"
};
throw new HttpResponseException(resp);
}
catch (Exception exc)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
return (byte[])image;
}
This returns an array with a length of 40133.
The calling Web API service looks like this:
public HttpResponseMessage Get(string invoice, string Customer)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream"));
byte[] img = client.GetByteArrayAsync("http://localhost:1363/api/Image/" + invoice + "/" + Customer).Result;
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new ByteArrayContent(img);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/tiff");
var disposition = new ContentDispositionHeaderValue("attachment");
disposition.FileName = "ImageDocument.tif";
response.Content.Headers.ContentDisposition = disposition;
return response;
}
However, the length of the img byte array is 53514.
When the browser tries to open the image, it tells me it is corrupt. If I open the TIFF in notepad, I get :
"SUkqAAgAAAASAP4ABAABAAAAAAAAAAABBAABAAAAsAYAAAEBBAABAAAAvgQAAAIBAwABAAAAAQAAAAMBAwABAAAABAAAAAYBAwABAAAAAAAAAAcBAwABAAAAAQAAABEBBAABAAAAAAMAABIBAwABAAAAAQAAABUBAwABAAAAAQAAABYBBAABAAAAvgQAABcBBAABAAAAxZkAABoBBQABAAAA+AIAABsBBQABAAAA8AIAACgBAwABAAAAAgAAADEBAgA4AAAAuAIAADIBAgAUAAAApAIAAOiAAwABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADIwMTI6MTA6MDMgMDc6Mjc6MTkAS29mYXggc3RhbmRhcmQgTXVsdGktUGFnZSBUSUZGIFN0b3JhZ2UgRmlsdGVyIHYzLjAzLjAwMADIAAAAAQAAAMgAAAABAAAALcMjAGC+cBQRwhOKcIuzqZDzrHoxF8k+VAOR2cAgjhC5lQEI+VYoiIiIiIiIiJXLAazgaZvMBqEcNI0BoJwaTMGsjgqGA1yOGaUA0Hg0igC5qZ6I1GSsNMuGqeBrI+bBoNYQrfNIiMREWdl4zVWRERkQzVECBpNcRyMCz6PhQgZwQGQLjpCIWwgxERGLLYAx//zLWLx4IeDnBnxSGFMRgIeZ4zcaR+KuPM4KeZ6MBTqKcj8YjAQ4IejDoQ4eE07WGnra3p9w07Xhw1s7NHu+0/v+/SQf6/9+cjwp0Z0Z8KeCm4p4IGQwhoz4cwCFBZN8u8s5duXeXTLva7pN6J56l45sf8u8u
SNIP*
Anyone know what I am doing wrong?
Thanks!
Chris
Solved
If anyone is interested in the calling code that leverages the solution identified, here it is:
public HttpResponseMessage Get(string invoice, string Customer)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("image/tiff"));
byte[] img = client.GetByteArrayAsync("http://localhost:1363/api/Image/" + invoice + "/" + Customer).Result;
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new ByteArrayContent(img);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/tiff");
var disposition = new ContentDispositionHeaderValue("attachment");
disposition.FileName = "ImageDocument.tif";
response.Content.Headers.ContentDisposition = disposition;
return response;
}

With your above current return type (byte[]) of action, formatters of web api are probably handling them and hence you are seeing unexpected response.
can you try sending the image as a ByteArrayContent instead?(you need to have HttpResponseMessage as a return type here)
Example:
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new ByteArrayContent(..your byte array here...);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");
return response;

Related

Unable to rectify VeraCode CWE ID 918 - (SSRF) in ASP.NET

Long story short, no matter what I try VeraCode continues to flag 8 lines of my code as flaws with CWE 918. This is old code so I'm not sure why it's suddenly being flagged.
Here's an example [offending] method with the flagged line in bold
public virtual async Task<HttpResponseMessage> Put(string controller = "", Dictionary<string, object> parameters = null, object body = null)
{
if (string.IsNullOrWhiteSpace(ApiBaseUrl)) return null;
HttpResponseMessage response = null;
using (var client = GetHttpClient())
{
client.BaseAddress = new Uri(ApiBaseUrl);
if (!string.IsNullOrEmpty(Token)) client.DefaultRequestHeaders.Add("Token-Key", Token);
if (!string.IsNullOrEmpty(DeviceId)) client.DefaultRequestHeaders.Add("DeviceId", DeviceId);
var url = GenerateUrl(controller, parameters);
var requestBody = GeneratedHttpContent(body);
if (requestBody == null) requestBody = new StringContent("");
**response = await client.PutAsync(url, requestBody);**
await LogError(response);
return response;
}
}
Here's my proposed fix that utilized an extension method to validate the URL
var url = GenerateUrl(controller, parameters);
var requestBody = GeneratedHttpContent(body);
if (requestBody == null) requestBody = new StringContent("");
**if (url.IsValidUrl())
{
response = await client.PutAsync(url, requestBody);
}
else
{
response = new HttpResponseMessage(HttpStatusCode.BadRequest);
}**
await LogError(response);
return response;
Here is the extension method with a VeraCode attribute
[RedirectUrlCleanser]
public static bool IsValidUrl(this string source)
{
return Uri.TryCreate(source, UriKind.RelativeOrAbsolute, out Uri uriResult) && Uri.IsWellFormedUriString(source, UriKind.RelativeOrAbsolute);
}
I can have VeraCode automatically mitigate based on the attribute, but our client will be performing their own scan and certainly won't have that setting enabled.
Any ideas on how I can resolve this would be appreciated.
The true source of the flaw is inside of your GenerateUrl method which is unfortunately not shown, but here is the general idea of what the Veracode is complaining about.
For CWE ID 918 it is hard to make Veracode recognize your fix unless you have static URL. You need to validate all your inputs that become parts of your request URL.
Below is what I found at the Veracode site:
https://community.veracode.com/s/question/0D52T00004i1UiSSAU/how-to-fix-cwe-918-veracode-flaw-on-webrequest-getresponce-method
The complete solution existed only for the case where you have single or some small number of possible input values (white list):
public WebResponse ProxyImage(string image_host, string image_path)
{
string validated_image_host = AllowedHosts.Host1;
if (image_host.Equals(AllowedHosts.Host2))
validated_image_host = AllowedHosts.Host2;
string validated_image = AllowedImages.Image1;
if (image_path.Equals(AllowedImages.Image2))
validated_image = AllowedImages.Image2;
string url = $"http://{validated_image_host}.example.com/{validated_image}";
return WebRequest.Create(url).GetResponse();
}
If the set of possible valid values is too large for that kind of validation then you need to fix the flaw by implementing dynamic validation of inputs using regular expressions. Unfortunately, Veracode is not smart enough to recognize that kind of fix, so "mitigation by design" is still required.
public WebResponse ProxyImage(string image_host, string image_path)
{
var image_host_regex = new System.Text.RegularExpressions.Regex("^[a-z]{1,10}$");
if (!image_host_regex.Match(image_host).Success)
throw new ArgumentException("Invalid image_host");
var image_path_regex = new System.Text.RegularExpressions.Regex("^/[a-z]{1,10}/[a-z]{1,255}.png$");
if (!image_path_regex.Match(image_path).Success)
throw new ArgumentException("Invalid image_host");
string url = $"http://{image_host}.example.com/{image_path}";
return WebRequest.Create(url).GetResponse();
}
Another way to fix this issue (which is kind of a hack) is to append your query string parameters in the baseAddress of the HttpClient, this way the veracode will not treat it like a flaw.
Here is how the solution would look like
public async Task<Data> GetData(string input)
{
try
{
var httpClient = new HttpClient();
//Appended the parameter in base address to
//to fix veracode flaw issue
httpClient.BaseAddress = new Uri($"https://someurl.com/somefunction/{input}");
//passing empty string in GetStringAsync to make sure
//veracode doesn't treat it like modifying url
var content = await httpClient.GetStringAsync("");
return JsonConvert.DeserializeObject<Data>(content);
}
}

Redirect from one WebAPI to an another WebAPI and getting the response

I want to create lets say a master/core api.Want to check for a certain parameter value and redirect to a an external api hosted in the same server.I have an api with uri http://hello.test.com/auth which takes two auth params Username and Password.Now i add a third parameter lets say Area.
{
"Username":"jason",
"Password":"bourne",
"Area":"mars"
}
Now coming to the master api, if with this uri for example http://master.test.com/v1/mster and i pass Username, Password and Area,and if the Area has value of lets say "mars" it should call the external mars api having uri http://mars.test.com/auth ,do the auth the process and return the response in the master api.is this possible?
With my /auth api i have this controller returning the response :
[HttpPost]
[Route(ApiEndpoint.AUTH)]
public HttpResponseMessage Auth(Login authBDTO)
{
if (!ModelState.IsValid)
return Request.CreateResponse(HttpStatusCode.BadRequest, ModelState);
using (AccountBusinessService accountService = new AccountBusinessService())
{
var result = accountService.Auth(authBDTO);
return Request.CreateResponse(HttpStatusCode.OK, result);
}
}
Any Help Appreciated.Couldnt find this exact scenario in here.Sorry if too naive.
Found a workaround.This did the work.
[Route(ApiEndpoint.SAS)]
public IHttpActionResult esp(Login auth)
{
if (auth.Coop == "PMC")
{
var httpWebRequest = (HttpWebRequest)WebRequest.Create("http://localhost:60069/api/v1/auth");
httpWebRequest.ContentType = "application/json";
httpWebRequest.Method = "POST";
using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
{
string json = new JavaScriptSerializer().Serialize(new
{
Username = auth.UserName,
Password = auth.Password
});
streamWriter.Write(json);
}
var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
dynamic obj = JsonConvert.DeserializeObject<ExpandoObject>(result);
obj.BaseUrl = "http://localhost:60069/api/v1";
return Ok(obj);
}
}

Pass LIST object by Rest API Client to Web API 2 using JSON

I have a problem in pass data list from Client to the Web API 2 by JSON.
Here are my code samples
Client
string RestUrl = "http://***SrviceUrl***/api/InvoiceHeader/{0}";
var uri = new Uri(string.Format(RestUrl, string.Empty));
List<InvItem> myList = new List<InvItem>(Common.invitems);
var json = JsonConvert.SerializeObject(myList);
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = null;
response = await client.PutAsync(uri ,content);
if (response.IsSuccessStatusCode)
{
Debug.WriteLine(#"successfully saved.");
}
Web Service - Controller class
[HttpPut]
[BasicAuthentication(RequireSsl = false)]
public HttpResponseMessage Put(string item)
{
List<InvItemToSave> myList = new List<InvItemToSave>();
myList = JsonConvert.DeserializeObject<List<InvItemToSave>>(item);
try
{
todoService.InsertInvoiceDetail(myList);
}
catch (Exception)
{
return base.BuildErrorResult(HttpStatusCode.BadRequest, ErrorCode.CouldNotCreateItem.ToString());
}
return base.BuildSuccessResult(HttpStatusCode.Created);
}
when i try to pass single data object to same controller it works fine.
but for LIST objects it returns error code.
StatusCode: 405, ReasonPhrase: 'Method Not Allowed'
I tried to pass exact same * list content* through third party REST client . It returned success code.
You are doing a PutAsync into a HttpPost action. Your api URL looks incorrect as well, should be,
http://***SrviceUrl***/api/InvoiceHeader
and action should be,
public HttpResponseMessage Put(List<InvItem> items)

How do I use response from an web api returning an image?

I have two web asp.net mvc based projects.
The first one has an image preview api, is implemented somewhat like this...
private async Task <HttpResponseMessage> GetImage(int id)
{
string filePath = "abstractedforsimplicity.png";
using(var file = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
{
byte[] buff = new byte[file.Length];
await file.ReadAsync(buff, 0, (int) file.Length);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(buff)
};
result.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
return result;
}
}
this works, I am able to show a preview with the following url - domain/api/image/3
Now I am a different application where I want to again make use of the same preview. I do not want to expose this api directly, so will be making a proxy api which will be making the call internally.
public HttpResponseMessage GetImage(int id)
{
string tempalteUrl = string.Format("{0}/{1}", ConfigurationManager.AppSettings["rmgpubadmin:template-base-url"], id);
WebClient client = new WebClient();
byte[] bytes = client.DownloadData(tempalteUrl);
// not very sure what should i do here ??
return null;
}
I tried to converting the bytes to an object, but if fails with errors - System.Runtime.Serialization.SerializationException.
The input stream is not a valid binary format. The starting contents (in bytes) are: 89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00 ...
What should i be doing here?

Live broadcast of the video site with Asp.Net WebForms + WebApi + HTML5

The problem is this:
on the server have a video file;
The administrator runs it on the play (video broadcast begins);
user is connected to the server - must be given to the video stream that is currently playing. A live webcast in real time.
To implement this task, I took as a basis for the article:
http://www.strathweb.com/2013/01/asynchronously-streaming-video-with-asp-net-web-api/
It worked. To give the stream a video file independently and in parallel.
I was looking on.
Next it was necessary to solve the problem of broadcasting to multiple customers (paragraph 3 in the job). I took this article:
http://gigi.nullneuron.net/gigilabs/streaming-data-with-asp-net-web-api-and-pushcontentstream/
Since I have to give evidence in the video byte - I replaced the StreamWriter class to Stream.
It works for one of the first client.
I made a website Asp.Net WebForms + WebApi + HTML5.
Web page - to run a video manager and viewed by users.
WebApi gives the player for <video> (HTML5) video stream.
HTML5:
<video>
<source src="http://localhost:8080/SiteVideoStreaming/api/live/?filename=nameFile" />
</video>
WebApi controllers:
//Controllers
public class LiveController : ApiController
{
private static ConcurrentBag<Stream> clients; // List of clients who need to simultaneously deliver video data
static string fileName = "";
static LiveController()
{
clients = new ConcurrentBag<Stream>();
WriteToStream(); // The first call - start to play a video file
}
[HttpGet]
public HttpResponseMessage Subscribe(string filename)
{
fileName = HostingEnvironment.MapPath("~/Videos/") + filename;
var response = Request.CreateResponse();
response.Content = new PushStreamContent((a, b, c) => { OnStreamAvailable(a, b, c); }, "video/mp4");
return response;
}
private void OnStreamAvailable(Stream stream, HttpContent content, TransportContext context)
{
clients.Add(stream); // Add new client
}
//Class record a video file into a streams
public async static void WriteToStream()
{
var buffer = new byte[65536];
using (var video = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
var length = (int)video.Length;
var bytesRead = 1;
while (length > 0 && bytesRead > 0)
{
bytesRead = video.Read(buffer, 0, Math.Min(length, buffer.Length));
foreach (var client in clients)// Each client in turn we return video data
{
try
{
await client.WriteAsync(buffer, 0, bytesRead); // ERROR - here !!! when you connect a second client
await client.FlushAsync();
}
catch (Exception ex)
{
Stream ignore;
clients.TryTake(out ignore);
}
}
length -= bytesRead;
}
}
}
}
If the request first came from one client - is given to video. Working.
If the request from the second client - when you try to start to give him a stream error occurs.
In this connection drops and the first client.
The error is as follows:
[System.Web.HttpException] = {"The remote host closed the connection.
The error code is 0x800704CD."}
As I understood after a search on the Internet is:
0x800704CD "An operation was attempted on a nonexistent network
connection."
Tell me that I'm not doing right?
Thank you.
I do so.
I use this controller:
public class VideoController : ApiController
{
// GET api/<controller>
public HttpResponseMessage Get(string filename)
{
if (filename == null)
return new HttpResponseMessage(HttpStatusCode.BadRequest);
string filePath = HostingEnvironment.MapPath("~/Videos/") + filename;
if (Request.Headers.Range != null)
{
//Range Specifc request: Stream video on wanted range.
try
{
//NOTE: ETag calculation only with file name is one approach (Not the best one though - GUIDs or DateTime is may required in live applications.).
Encoder stringEncoder = Encoding.UTF8.GetEncoder();
byte[] stringBytes = new byte[stringEncoder.GetByteCount(filePath.ToCharArray(), 0, filePath.Length, true)];
stringEncoder.GetBytes(filePath.ToCharArray(), 0, filePath.Length, stringBytes, 0, true);
MD5CryptoServiceProvider MD5Enc = new MD5CryptoServiceProvider();
string hash = BitConverter.ToString(MD5Enc.ComputeHash(stringBytes)).Replace("-", string.Empty);
HttpResponseMessage partialResponse = Request.CreateResponse(HttpStatusCode.PartialContent);
partialResponse.Headers.AcceptRanges.Add("bytes");
partialResponse.Headers.ETag = new EntityTagHeaderValue("\"" + hash + "\"");
var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
partialResponse.Content = new ByteRangeStreamContent(stream, Request.Headers.Range, new MediaTypeHeaderValue("video/mp4"));
return partialResponse;
}
catch (Exception ex)
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
else
{
return new HttpResponseMessage(HttpStatusCode.RequestedRangeNotSatisfiable);
}
}
}
On the client side - I run it <video> video player through technology SignalR.

Resources