Prevent XSS on webapi - security

I have a webapi post method which accepts a json and returns a record.
public ProductResponse Post(ProductRequest reqObj){
//search the record
//var responseObject = //search in db using ProductRequest.Name
return responseObject;
}
public class ProductRequest{
public string Name {get;set;}
}
so if the request comes in like {"Name": "iPhone"} it looks it up and return the response {"Name": "iPhone", "price": "25.00", "Description":"lorem ipsum"}, if the product doesn't exist then it returns {"Name": "iPhone", "error": "product doesn't exists"}.
I've been told that the api is prone to XSS attack, because when the user sends {"Name": "iPhone<script>alert(1);</script>"} then response he is getting is {"Name": "iPhone<script>alert(1);</script>", "error": "product doesn't exists"}. The expectation is that the tags should have been encoded.
Question 1: Is it vulnerable to XSS when I am not rendering the content directly to the browser?
Question 2: If I am encoding the response, then I have to put a decoder when I get the request too?

It does not matter how you intended to render or use the response, the thing with these attacks is what is possible for an attacker, like for example sending a link to a victim user, displaying your api response in a browser.
However, this is likely not exploitable in recent browsers and given some circumstances. Most importantly, a fairly recent browser only renders a response as html (and runs scripts) if the content type of your response is html (eg. text/html). A json response should have its content type set to application/json, in which case javascript would not be run, and it is not vulnerable to xss. (Well, except in very old browsers, but nobody should be using those anymore, a browser from the past ~10 years should be ok.)
Encoding values in json responses is usually wrong. The reason is that it is not the responsibility of the backend to figure out how the value will be used in the frontend, and for that, what encoding is needed. The frontend might want to render it in html, or in a html attribute, or create an xml, or a csv, all needing a potentially different kind of encoding... So appropriate, context-aware encoding should be performed on the client (most modern client-side frameworks take care of this mostly, but not entirely).
You do need to take care though to encode values in the response for the json itself (meaning dealing with quotes for example, so the response is not corrupted due to a quote in the value). But this has nothing to do with xss, and is likelt already managed by the framework you use to generate the json response.

Related

Why odata Trippinservice returns only 8 pages?

When I give the odata service url,https://services.odata.org/V4/(S(ysqt4lcalbsipb1qkoc04ryb))/TripPinServiceRW/People in browser,I only get 8 records.Why do I get only 8 records when there are total of 20 records in People entity?Is PageSize set in Trippinservice?Can anyone help me to understand this?
Yes, this service implements server-side paging.
Firstly we identify that server-side pagination is in effect from the presense of the #odata.nextLink property in the response, this is in the root of the response:
{
"#odata.context": "http://services.odata.org/V4/(S(ysqt4lcalbsipb1qkoc04ryb))/TripPinServiceRW/$metadata#People",
"#odata.nextLink": "https://services.odata.org/V4/(S(ysqt4lcalbsipb1qkoc04ryb))/TripPinServiceRW/People?%24skiptoken=8",
"value": [
... 8 records ...
]
}
We can only assUme that the page size is 8 by counting the number of records in the response or by consulting the documentation for the given service. In this case there is a $skiptoken query parameter inside the next link, in this case it happens to have a value of 8 and this corresponds to the number of records, but only by coincidence.
NOTE: %24 is a dollar sign $ that has been url encoded
~/TripPinServiceRW/People?%24skiptoken=8
In the case of the TripPin service documented in the OData Basic Tutorial, the $skiptoken value represents the number of records to skip and is a common implementation, but it is not a standard.
There is no specific mention of the server page size for the People feed in the documentation, nor is there a standard way to document $skiptoken logic at all in the $metatdata. Have a read on how and why ASP.NET WebAPI implements skiptoken: Use $skiptoken for server-driven paging
We can demonstrate this by navigating the next nextLink or altering the $orderby:
GET: ~/TripPinServiceRW/People?$skiptoken=8
{
"#odata.context": "http://services.odata.org/V4/(S(ysqt4lcalbsipb1qkoc04ryb))/TripPinServiceRW/$metadata#People",
"#odata.nextLink": "https://services.odata.org/V4/(S(ysqt4lcalbsipb1qkoc04ryb))/TripPinServiceRW/People?%24skiptoken=16",
"value": [
... 8 more records ...
]
}
The $skiptoken is now 16 in the new nextLink: ~/TripPinServiceRW/People?%24skiptoken=16. In many implementations the $skiptoken will represent the key value of the last record that was sent, but TripPin does not use this convention, we can verify that by changing the order:
Both of these responses will have $skiptoken=8 in the nextLink
GET: ~/TripPinServiceRW/People?$orderby=UserName
{
"#odata.context": "http://services.odata.org/V4/(S(ysqt4lcalbsipb1qkoc04ryb))/TripPinServiceRW/$metadata#People",
"#odata.nextLink": "https://services.odata.org/V4/(S(ysqt4lcalbsipb1qkoc04ryb))/TripPinServiceRW/People?%24orderby=UserName&%24skiptoken=8",
"value": [
... 8 records ...
]
}
GET: ~/TripPinServiceRW/People?$orderby=UserName desc
{
"#odata.context": "http://services.odata.org/V4/(S(ysqt4lcalbsipb1qkoc04ryb))/TripPinServiceRW/$metadata#People",
"#odata.nextLink": "https://services.odata.org/V4/(S(ysqt4lcalbsipb1qkoc04ryb))/TripPinServiceRW/People?%24orderby=UserName+desc&%24skiptoken=8",
"value": [
... 8 records ...
]
}
According to the specification, $skiptoken is an arbitrary token that services can use to retrieve the next page of data from a previously prepared set. The value of the $skiptoken will have special significance to the server itself and it may be an arbitrary token or reference pointing to a page in a cached resultset.
11.2.5.7 Server-Driven Paging
Responses that include only a partial set of the items identified by the request URL MUST contain a link that allows retrieving the next partial set of items. This link is called a next link; its representation is format-specific. The final partial set of items MUST NOT contain a next link.
OData clients MUST treat the URL of the next link as opaque, and MUST NOT append system query options to the URL of a next link. Services may not allow a change of format on requests for subsequent pages using the next link. Clients therefore SHOULD request the same format on subsequent page requests using a compatible Accept header. OData services may use the reserved system query option $skiptoken when building next links. Its content is opaque, service-specific, and must only follow the rules for URL query parts.
It is worth highlighting this very specific note in the specification:
OData clients MUST NOT use the system query option $skiptoken when constructing requests.
The $skiptoken is a server-side implementation and in many cases you couldn't even guess what a correct value might be. The TripPin service is a very simple demonstration API, it uses a page size of 8 to illustrate the behaviour of server-side paging, given the small size of the overall dataset (20) this is a nice arbitrary number that will result in multiple pages with the last page only partially full. That's enough to test basic compliance of server-side supporting data interfaces.
Server-side paging is designed to encourage search driven interfaces where users formulate more precise search criteria in preference to browsing through the pages 1 by 1. Virtual Scrolling is a common user interface paradigm that exploits server-side paging. Different languages and runtimes have different implementations but basically the user can scroll through a list or grid of data and when they get to the bottom there might be a link to "load more" records (behind the scenes, this will load the response from the nextLink). Sometimes this link is not displayed and the data is automatically loaded as the user approaches or reaches the end of the list.
You can still use traditional client-side paging using the $top and $skip query parameters, however some service implementations of server-side paging will still constrain the results to the fixed number of rows as defined by that servers internal logic. If you are implementing client-side paging then you may still need to use the nextLink to retrieve all the results for each client-side page of results.
Lets compare by first getting page 2, of a client-side page size of 5:
GET: ~/TripPinServiceRW/People?$skip=5&$top=5
{
"#odata.context": "http://services.odata.org/V4/(S(ysqt4lcalbsipb1qkoc04ryb))/TripPinServiceRW/$metadata#People",
"value": [
... 5 records ...
]
}
Notice that there is no #odata.nextLink property in the response, that is because the requested number of items does not exceed the server page size logic. So lets try a page size of 9. This time, to retrieve all the records for the page, we will need to make multiple queries.
The general guidance here is to recursively call the service for each of the nextLink urls in the responses, if they contain a nextLink
GET: ~/TripPinServiceRW/People?$skip=9&$top=9
{
"#odata.context": "http://services.odata.org/V4/(S(ysqt4lcalbsipb1qkoc04ryb))/TripPinServiceRW/$metadata#People",
"#odata.nextLink": "https://services.odata.org/V4/(S(ysqt4lcalbsipb1qkoc04ryb))/TripPinServiceRW/People?%24skip=9&%24top=9&%24skiptoken=8",
"value": [
... 8 records ...
]
}
GET: ~/TripPinServiceRW/People?%24skip=9&%24top=9&%24skiptoken=8
{
"#odata.context": "http://services.odata.org/V4/(S(ysqt4lcalbsipb1qkoc04ryb))/TripPinServiceRW/$metadata#People",
"value": [
... 1 record ...
]
}
When you are implementing your own OData-v4 conformant API it is important to understand this quirk and to document specifically in your API documentation what your policy or convention is with regard to server-side paging and which collections it is enabled on.
In my own implementations, I will often disable server-side paging if the request contains the client-paging token $top (and the value for $top is <= 200) but
From a client-side implementation if you see a nextLink property in the response and you didn't recieve the expected number of records, then you should query for the subsequent server-pages to fulfil your request if you are not able to implement a virtual Scroll enabled user experience.
NOTE: Normally when we discuss paging in OData v4 services the examples would include use of the $count query option.
[11.2.9 Requesting the Number of Items in a Collection]: On success, the response body MUST contain the exact count of items matching the request after applying any $filter or $search system query options...
The returned count MUST NOT be affected by $top, $skip, $orderby, or $expand.
The TripPin service DOES NOT CONFORM to this particular (and many other) clause in the specification, so I have not used that query option in this explanation.

How to pass a unique user ID to a page with user-specific, personal data

I'm sending a mass email though Emma (3rd party vendor) that will contain a link to a landing page. The landing page will be personalized and display some of the user's identifying info (name, title, email). Additionally, there will be a form collecting a few of the user's preferences that will be saved back to that user's record in Emma's database.
The user ID column in the 3rd party's database is incremental so I obviously can't just append that value through the query string otherwise user 522, for example, would get a link such as www.example.com?landing/?uid=522 allowing him (or anyone with the link)cto take a wild guess at other values for uid (such as 523... or 444) and change other users' preferences as well as view their personal data quite easily.
Bottom line is that I'm trying to find a secure way to pass an ID (or other unique value) that I can look up via API and use to dynamically display and then resubmit personal info/data on this landing page on a user-to-user basis.
I had an idea to add a custom column to my list in Emma for a unique identifier. I would then write a script (accessing Emma's API) to BASE64 Encode the ID (or possibly email address, as that would be unique as well) and add that to the list for each user. In my email, I could then pass that to the landing page in for the form of ?xy=ZGF2ZUBidWRvbmsuY29t, but I know this is encoding and not encrypting so not all that secure... or secure at all for that matter.
To my knowledge, there's no remote risk of anyone receiving the mailing having the ability and/or inclination to know what those extra characters in the link are, BASE64 Decode, BASE64 ENCODE another email address or integer an make a request with the newly BASE64 encoded value in order to manipulate my system in an an unintended way.
BUT for the purpose of this question, I'd like to know the "right" way to do this or what levels of security are currently being taken in similar circumstances. I've read about JWT tokens and some OOth stuff, but I'm not quite sure that's possible given that I've got the Emma API to deal with as well... and/or if that is overkill.
What is appropriate/standard for passing values to a page that are in turn used for a form to be resubmitted along with other user-supplied values when giving the user the ability to submit a "compromised" (intentionally or not) form could, at worst, could cause one of their competitors to have bad preference and opt-in saved data in our Emma mailing list?
Security on the web is all about "acceptable risk". You can reduce risk in various ways, but ultimately there's always some risk exposure you must be willing to accept.
Your very best option would be to force users to be logged-in to view the page, and to avoid using any querystring parameters. That way the backend for the page can pull the ID (or whatever it might need) out of the server's session.
Your next best option still involves forcing the user to be logged in, but leave the uid in the URL -- just be sure to validate that the user has access to the uid (i.e. don't let a user access another user's info).
If you can't do that... then you could create random keys/ids that you store in a database, and use those values (rather than uid or email or real data) in the URL. BUT let's be clear: this isn't secure, as it's technically possible to guess/deduce the scheme.
Absolutely DO NOT try passing the info in the URL as base64 encoded data, that's likely to be the first thing a hacker will figure out.
Keep in mind that any unsecured API that returns PII of any kind will be abused by automated tools... not just a user farting around with your form.
To my knowledge, there's no remote risk of anyone receiving the
mailing having the ability and/or inclination to know
^ That's always always always a bad assumption. Even if the result is at worst something you think is trivial, it opens the door for escalation attacks and literally exposes the company to risks it likely doesn't want to accept.
If you're stuck between bad options, my professional advice is to have a meeting where you record the minutes (either video, or in a document) and have someone with "authority" approve the approach you take.
In case anyone needs a working example, I found this at https://bhoover.com/using-php-openssl_encrypt-openssl_decrypt-encrypt-decrypt-data/. It uses PHP's openssl_encrypt and openssl_decrypt, and it seems to work perfectly for my purposes
<?php
$key = base64_encode(openssl_random_pseudo_bytes(32));
function my_encrypt($data, $key) {
// Remove the base64 encoding from our key
$encryption_key = base64_decode($key);
// Generate an initialization vector
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
// Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
$encrypted = openssl_encrypt($data, 'aes-256-cbc', $encryption_key, 0, $iv);
// The $iv is just as important as the key for decrypting, so save it with our encrypted data using a unique separator (::)
return base64_encode($encrypted . '::' . $iv);
}
function my_decrypt($data, $key) {
// Remove the base64 encoding from our key
$encryption_key = base64_decode($key);
// To decrypt, split the encrypted data from our IV - our unique separator used was "::"
list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
return openssl_decrypt($encrypted_data, 'aes-256-cbc', $encryption_key, 0, $iv);
}
I first ran my_encrypt in a loop to encrypt the uid of each member in the list.
$members[$uid] = array('unique-identifier' => my_encrypt($uid, $key));
Next, through the API, I modified each member's record with the new value.
$ret = update_members_batch($members);
That only had to be done once.
Now in my email, I can pass the uid through the query string like this www.example.com/landing/?UID=<% unique-identifier %>, which will look something like www.example.com/landing/?UID= XXXXX2ovR2xrVmorbjlMMklYd0RNSDNPMUp0dmVLNVBaZmd3TDYyTjBFMjRkejVHRjVkSEhEQmlYaXVIcGxVczo6Dm3HmE3IxGRO1HkLijQTNg==
And in my page, I'll decrypt the query string value and use it via the API to get the email address with something like:
$member_email = get_member(my_decrypt($_GET['UID']))['email'];
and display it in the appropriate location(s) on my page.
I think this covers all my bases, but I am going to have a stakeholder meeting to get sign-off. What potential vulnerabilities does this expose that I should warn them about?

What kinds of security vulnerabilites can be instroduced by binding specifically GET request data to page model properties?

I'm reading tutorials on ASP .NET Core and Razor Pages.
One of them, when dealing with the BindProperty attribute, has remarks I find hardly understandable:
Razor Pages, by default, bind properties only with non-GET verbs.
Binding to properties can reduce the amount of code you have to write.
Binding reduces code by using the same property to render form fields
(<input asp-for="Customer.Name" />) and accept the input.
For security reasons, you must opt in to binding GET request data to
page model properties. Verify user input before mapping it to
properties. Opting in to this behavior is useful when addressing
scenarios which rely on query string or route values.
To bind a property on GET requests, set the [BindProperty] attribute's
SupportsGet property to true: [BindProperty(SupportsGet = true)]
(emphasis mine) Source: Introduction to Razor Pages in ASP.NET Core ยง Writing a basic form
I do not understand. Why do extra security measures need to be taken when dealing specifically with GET requests?
As far as I can tell, GET requests are supposed to be safer, not less safe than POST requests, because GET only retrieves data, while POST sends data to the server. So, more often than not, POST requests need extra security measures GET reqs don't need.
And yet now I'm reading that it's fine to do X with POST but careful, don't do this with GET recklessly, you must opt-in and be warned, take precautions!
Why are these warnings necessary? What kind of security vulnerabilities can be introduced by binding GET request data to page model properties? And why are these vulnerabilites not applicable to POST requests?
Binding is two-way model, meaning it binds a given element for both rendering ("show this value here") and submitting back ("save this value there"). For example, you'd use binding to populate an input field with a value that a user could then update and POST back. Binding handles dealing with the value automatically.
If you simply want to display a value, you don't need to use binding at all. Simply make it a public property of your model and reference it directly ({Model.PropertyName}).
There are a bunch of reasons for avoiding [BindProperty(SupportsGet = true)] but I think HTTP's RFC 7231, Section 9.4 covers it well:
URIs are intended to be shared, not secured, even when they identify secure resources. URIs are often shown on displays, added to templates when a page is printed, and stored in a variety of unprotected bookmark lists. It is therefore unwise to include information within a URI that is sensitive, personally identifiable, or a risk to disclose.
Authors of services ought to avoid GET-based forms for the submission of sensitive data because that data will be placed in the request-target. Many existing servers, proxies, and user agents log or display the request-target in places where it might be visible to third parties. Such services ought to use POST-based form submission instead.
Since the Referer header field tells a target site about the context that resulted in a request, it has the potential to reveal information about the user's immediate browsing history and any personal information that might be found in the referring resource's URI.

Instagram min_tag_id leaves out posts

I am using the Instagram real-time API to get the most recent posts of a certain hashtag. Therefore I subscribed to the API and Instagram makes a callback everytime a post gets tagged with that hashtag.
Ever 5 seconds my application checks if there where any callbacks (I register every callback to make sure, that I don't make any requests to Instagram if there where no callbacks before)
When there was at least one callback, I make a request to the "tags/media/recent" endpoint. Additionaly I store the min_tag_id of every response and send it with the next request. That way Instagram only sends me the "newest" content.
So far so good.
Instagram is "kind" enough to indicate the min_tag_id I should use next. In a normal response it should look like this (I shortened the response a little):
"pagination":{
"min_tag_id": "1061443126713008625" // the min_tag_id I can use for the next request
},
"data":[
{
"id" : "1061443126713008625_782775143", // id of the first post
// rest of the data...
},{
"id" : "1061443123569823070_176952626", // id of another post
// rest of the data...
}
]
As you can see, the min_tag_id given by Instagram is part of the id of the first post.
"min_tag_id":"1061443126713008625"
"id":1061443126713008625_782775143"
So next time I make a request with that min_tag_id, I receive all posts coming afterwards.
But
Sometimes Instagram gives me a min_tag_id but doesn't send me the post with that id. Like so:
"pagination": {
"min_tag_id": "1061443926677909216"
}
"data":[
{
"id" : "1061443924303843601_1666507083"
// ...
},{
"id" : "1061443905925893282_479418538"
// ...
}
]
And the next time I use the new min_tag_id I get all the posts coming afterwards but not the post with that id.
In short I am missing some posts when using the pagination.
Is there anything that I am doing wrong? Is the pagination not meant for that?
Sometimes Instagram doesn't send me anything at all (besides the pagination) and sometimes the post with the min_tag_id is contained in a response afterwards.
If I am not doing anything wrong and if I just have to live with it, does anyone know a way to get the real id of the post with just the min_tag_id ? As you can see they are not the same and I don't know the last part (***_1666507083)
This seems to be a known issue, as discussed here.
Have you tried passing in a large count parameter as suggested by #Amir
You will also need to pass a large count parameter and follow the
pagination of the response to receive all data without losing anything
when the speed of tagging is faster than your polling.
Source: https://stackoverflow.com/a/29877510/325521

How to get previous URL in JSF using FacesContext?

I need to get the redirected URL or id in JSF using FacesContext. For current URL, I'm using.
String currentPage = FacesContext.getCurrentInstance().getViewRoot().getViewId();
Closest you can get is the referer header via ExternalContext#getRequestHeaderMap():
String referrer = externalContext.getRequestHeaderMap().get("referer");
// ...
You should only keep in mind that this is a client-controlled value and can thus be fully spoofed from client side on (i.e. the enduser can easily edit or even remove it).
Even then, there are cases where the client application won't send it along. For an overview, see among others this question: In what cases will HTTP_REFERER be empty.
Depending on the functional requirement, you'd better manually pass it along as request parameter, or store it in view or session scope.

Resources