I've run a pen test tool (Burp) against my node(express)/angular application and it identified a reflected XSS vulnerability specifically when attempting a GET request for static assets (noticeably vulnerabilities were not found for any of the requests being made when a user interacts with the application).
The issue detail is:
The name of an arbitrarily supplied URL parameter is copied into a
JavaScript expression which is not encapsulated in any quotation
marks. The payload 41b68(a)184a9=1 was submitted in the name of an
arbitrarily supplied URL parameter. This input was echoed unmodified
in the application's response.
This behavior demonstrates that it is possible to inject JavaScript
commands into the returned document. An attempt was made to identify a
full proof-of-concept attack for injecting arbitrary JavaScript but
this was not successful. You should manually examine the application's
behavior and attempt to identify any unusual input validation or other
obstacles that may be in place.
The vulnerability was tested by passing an arbitrary url parameter to the request like so:
GET /images/?41b68(a)184a9=1
The response was:
HTTP/1.1 404 Not Found
X-Content-Security-Policy: connect-src 'self'; default-src 'self'; font-src 'self'; frame-src; img-src 'self' *.google-analytics.com; media-src; object-src; script-src 'self' 'unsafe-eval' *.google-analytics.com; style-src 'self' 'unsafe-inline'
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
Strict-Transport-Security: max-age=10886400; includeSubDomains; preload
X-Download-Options: noopen
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 52
Date: Wed, 08 Oct 2015 10:46:43 GMT
Connection: close
Cannot GET /images/?41b68(a)184a9=1
You can see that I have CSP in place (using Helmet to implement) and other protections against exploits. The app is served over https, but no user auth is required. CSP restricts request to the app's domain only plus google analytics.
The pen test report advises validating input (I am, but surely that would make requests including data sent by a user unsafe if I wasn't?), and encoding html which angular does by default.
I'm really struggling to find a solution to preventing or mitigating this for those requests for static assets:
Should I whitelist all requests for my application under csp?
Can I even do this, or will it only whitelist domains?
Can/should all responses from node/express to requests for static assets be encoded in some way?
The report states that "The name of an arbitrarily supplied URL parameter is copied into a JavaScript expression which is not encapsulated in any quotation marks". Could this expression be somewhere in the express code that handles returning static assets?
Or that GET request param can somehow be evaluated in my application code?
Update
Having done some investigation into this it seems that at least part of the mitigation is to escape data in url param values and sanitize the input in the url.
Escaping of the url is already in place so:
curl 'http://mydomain/images/?<script>alert('hello')</script>'
returns
Cannot GET /images/?<script>alert(hello)</script>
I've also put express-sanitized in place on top of this.
However, if I curl the original test the request param is still reflected back.
curl 'http://mydomain/images/?41b68(a)184a9=1'
Cannot GET /images/?41b68(a)184a9=1
Which you would expect because html is not being inserted into the url.
The responses to GET requests for static assets are all handled by app.use(express.static('static-dir')) so the query is passed into this. express.static is based on serve-static which depends on parseurl.
The cause of the issue is that for invalid GET requests express will return something like:
Cannot GET /pathname/?yourQueryString
Which in many cases is a valid response, even for serving static assets. However, in my case and I'm sure for others the only valid requests for static assets will be something like:
GET /pathname/your-file.jpg
I have a custom 404 handler that returns a data object:
var data = {
status: 404,
message: 'Not Found',
description: description,
url: req.url
};
This is only handled for invalid template requests in app.js with:
app.use('/template-path/*', function(req, res, next) {
custom404.send404(req, res);
});
I've now added explicit handlers for requests to static folders:
app.use('/static-path/*', function(req, res, next) {
custom404.send404(req, res);
});
Optionally I could also strip out request query params before the 404 is returned:
var data = {
status: 404,
message: 'Not Found',
description: description,
url: url.parse(req.url).pathname // needs a var url = require('url')
};
Related
I created an API endpoint using Google Cloud Functions and am trying to call it from a JS fetch function.
I am running into errors that I am pretty sure are related to either CORS or the output format, but I'm not really sure what is going on. A few other SO questions are similar, and helped me realize I needed to remove the mode: "no-cors". Most mention enabling CORS on the BE, so I added response.headers.set('Access-Control-Allow-Origin', '*') - which I learned of in this article - to ensure CORS would be enabled... But I still get the "Failed to fetch" error.
The Full Errors (reproducible in the live demo linked below) are:
Uncaught Error: Cannot add node 1 because a node with that id is
already in the Store. (This one is probably unrelated?)
Access to fetch at
'https://us-central1-stargazr-ncc-2893.cloudfunctions.net/nearest_csc?lat=37.75&lon=-122.5'
from origin 'https://o2gxx.csb.app' has been blocked by CORS policy:
Request header field access-control-allow-origin is not allowed by
Access-Control-Allow-Headers in preflight response.
GET
https://us-central1-stargazr-ncc-2893.cloudfunctions.net/nearest_csc?lat=37.75&lon=-122.5 net::ERR_FAILED
Uncaught (in promise) TypeError: Failed to fetch
See Code Snippets below, please note where I used <---- *** Message *** to denote parts of the code that have recently changed, giving me one of those two errors.
Front End Code:
function getCSC() {
let lat = 37.75;
let lng = -122.5;
fetch(
`https://us-central1-stargazr-ncc-2893.cloudfunctions.net/nearest_csc?lat=${lat}&lon=${lng}`,
{
method: "GET",
// mode: "no-cors", <---- **Uncommenting this predictably gets rid of CORS error but returns a Opaque object which seems to have no data**
headers: {
// Accept: "application/json", <---- **Originally BE returned stringified json. Not sure if I should be returning it as something else or if this is still needed**
Origin: "https://lget3.csb.app",
"Access-Control-Allow-Origin": "*"
}
}
)
.then(response => {
console.log(response);
console.log(response.json());
});
}
Back End Code:
import json
import math
import os
import flask
def nearest_csc(request):
"""
args: request object w/ args for lat/lon
returns: String, either with json representation of nearest site information or an error message
"""
lat = request.args.get('lat', type = float)
lon = request.args.get('lon', type = float)
# Get list of all csc site locations
with open(file_path, 'r') as f:
data = json.load(f)
nearby_csc = []
# Removed from snippet for clarity:
# populate nearby_csc (list) with sites (dictionaries) as elems
# Determine which site is the closest, assigned to var 'closest_site'
# Grab site url and return site data if within 100 km
if dist_km < 100:
closest_site['dist_km'] = dist_km
// return json.dumps(closest_site) <--- **Original return statement. Added 4 lines below in an attempt to get CORS set up, but did not seem to work**
response = flask.jsonify(closest_site)
response.headers.set('Access-Control-Allow-Origin', '*')
response.headers.set('Access-Control-Allow-Methods', 'GET, POST')
return response
return "No sites found within 100 km"
Fuller context for code snippets above:
Here is a Code Sandbox Demo of the above.
Here is the full BE code on GitHub, minus the most recent attempt at adding CORS.
The API endpoint.
I'm also wondering if it's possible that CodeSandbox does CORS in a weird way, but have had the same issue running it on localhost:3000, and of course in prod would have this on my own personal domain.
The Error would appear to be CORS-related ( 'https://o2gxx.csb.app' has been blocked by CORS policy: Request header field access-control-allow-origin is not allowed by Access-Control-Allow-Headers in preflight response.) but I thought adding response.headers.set('Access-Control-Allow-Origin', '*') would solve that. Do I need to change something else on the BE? On the FE?
TLDR;
I am getting the Errors "Failed to fetch" and "field access-control-allow-origin is not allowed by Access-Control-Allow-Headers" even after attempts to enable CORS on backend and add headers to FE. See the links above for live demo of code.
Drop the part of your frontend code that adds a Access-Control-Allow-Origin header.
Never add Access-Control-Allow-Origin as a request header in your frontend code.
The only effect that’ll ever have is a negative one: it’ll cause browsers to do CORS preflight OPTIONS requests even in cases when the actual (GET, POST, etc.) request from your frontend code would otherwise not trigger a preflight. And then the preflight will fail with this message:
Request header field Access-Control-Allow-Origin is not allowed by Access-Control-Allow-Headers in preflight response
…that is, it’ll fail with that unless the server the request is being made to has been configured to send an Access-Control-Allow-Headers: Access-Control-Allow-Origin response header.
But you never want Access-Control-Allow-Origin in the Access-Control-Allow-Headers response-header value. If that ends up making things work, you’re actually just fixing the wrong problem. Because the real fix is: never set Access-Control-Allow-Origin as a request header.
Intuitively, it may seem logical to look at it as “I’ve set Access-Control-Allow-Origin both in the request and in the response, so that should be better than just having it in the response” — but it’s actually worse than only setting it in the response (for the reasons described above).
So the bottom line: Access-Control-Allow-Origin is solely a response header, not a request header. You only ever want to set it in server-side response code, not frontend JavaScript code.
The code in the question was also trying to add an Origin header. You also never want to try to set that header in your frontend JavaScript code.
Unlike the case with the Access-Control-Allow-Origin header, Origin is actually a request header — but it’s a special header that’s controlled completely by browsers, and browsers won’t ever allow your frontend JavaScript code to set it. So don’t ever try to.
Angular version is 1.3.13
I'm making a url request to a public url to get some data. For one url it functions but for the other it does not. I have no control over the server side.
The URL returns with 200 but AngularJS passes it to the error function. In my browser console the data is there, as JSON, but I do not have access to it in Angular. I was thinking it might the the http.get calling something before passing the data on.
I've tried with plain $http and a custom transformResponse but it still falls to the error of the custom response.
This is the URL:
https://uatmerchant.sixdots.be/oidc/.well-known/openid-configuration
These are the response headers from the URL:
Connection
close
Content-Security-Policy
reflected-xss block
Content-Type
application/json
Date
Mon, 15 Jan 2018 12:33:30 GMT
Set-Cookie
BIGipServer~DMZ~pool_uat_5000=…omain=.uatmerchant.sixdots.be
Strict-Transport-Security
max-age=15552000; includeSubDomains
Transfer-Encoding
chunked
X_CORRELATION_ID
UAT-MER-F5-20180115133330632
X-Content-Security-Policy
reflected-xss block
X-Content-Type-Options
nosniff
X-Frame-Options
SAMEORIGIN
X-Xss-Protection
1; mode=block
This is part of the data that is in the response in the browser console
request_parameter_supported true
claims_parameter_supported false
scopes_supported […]
0 openid
1 profile
2 email
3 address
4 phone
issuer https://uatmerchant.sixdots.be/oidc
acr_values_supported […]
0 tag:sixdots.be,2016-06:acr_basic
Here is the code making the call with the URL that is detailed at the start of this post above
var oohahhel = $sce.trustAsResourceUrl(urlVar);
$http.get(oohahhel)
.then(
function success(response){
var jsonResponse = angular.fromJson(response);
//process response
}
,function error(reason){
//process error
});
I have a Jersey REST application in Websphere 8.5. Whenever my resource responds with a 4xx response, Webpshere (or IBM Http Server) overwrites the response body with its default error message, for example:
Error 404: Not Found
Not only don't I want the response body to be overwritten, I want the response body as produced by my resource, but also Websphere does not update the Content-Length: response header, thereby creating an inconsistency between content-length and the actual response body length.
Is there a way to force Websphere (or IBM HTTP server) to not overwrite the response body when my resource produces a 4xx response?
For example a call to the following resource:
#Path("timeout")
public class TimeoutService {
#GET
#Path("withbody")
#Produces(MediaType.APPLICATION_JSON)
public Response getWithBody() {
Response.ResponseBuilder builder = Response.status(Response.Status.NOT_FOUND);
builder.entity("{ \"status\" : \"notok\" }");
return builder.build();
}
}
will result in:
Error 404: No
Request Method:GET
Status Code:404 Not Found
Response Headers
$WSEP:
Connection:Keep-Alive
Content-Language:en-US
Content-Length:13
Content-Type:text/html;charset=ISO-8859-1
Date:Tue,21 Apr 2015 11:50:38 GMT>
Keep-Alive:timeout=15, max=100
X-Powered-By:Servlet/3.0
Note how the default message gets truncated because of the inconsistent content length.
But what I want is this call to respond with a 404 and { "status" : "notok" } and Content-Type set to application/json
Yes, you can. Here is the page that outlines the Jersey property that needs to be changed to disable WAS hi-jacking your errors:
https://java.net/jira/browse/JERSEY-2521
In short, you have to set the property org.glassfish.jersey.server.ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR to true when you configure Jersey.
I'm trying to set a HTTP Header for all my REST calls with following code:
app.factory('authInterceptor', function ($rootScope, $q, $window) {
return {
request: function (config) {
config.headers = config.headers || {};
config.headers.Authorization = '12345678';
return config;
},
response: function (response) {
if (response.status === 401) {
// handle the case where the user is not authenticated
}
return response || $q.when(response);
}
};
});
app.config(function ($httpProvider) {
$httpProvider.interceptors.push('authInterceptor');
});
I currently don't have any authorization enabled on the server.
when I leave out the line "config.headers.Authorization = '12345678';" , then the REST call works well and I get my results. In the JS console I see
GET http://localhost:8080/rest/club/1 [HTTP/1.1 200 OK 7ms]
But when I put this line in to set the Header field, then I see following request in the javascript console
OPTIONS http://localhost:8080/rest/club/1 [HTTP/1.1 200 OK 2ms]
Why does setting Authorization Header change my method from "GET" to "OPTIONS"? And how can I set a custom Header and my request still work?
changing it to
config.headers["X-Testing"] = '12345678';
had the same result.
EDIT:
I tried the answer, I'm setting following HTTP Headers in the server:
response.getHeaders().putSingle("Access-Control-Allow-Origin", "http://localhost");
response.getHeaders().putSingle("Access-Control-Allow-Header", "X-Testing");
response.getHeaders().putSingle("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
response.getHeaders().putSingle("Access-Control-Max-Age", 1728000);
my REST server is running on port 8080, the webserver for the html/JS on port 8000 (initially worked with file://... but moved to a separate webserver because Origin was null)
response.getHeaders().putSingle("Access-Control-Allow-Origin", "*");
or
response.getHeaders().putSingle("Access-Control-Allow-Origin", "http://localhost:8000");
didn't work either.
Must I return any content in the OPTIONS response? I tried 200 OK with the same content as the GET, but I also tried 204 No Content.
2nd EDIT:
here is what firefox sends and receives for the OPTIONS method:
You need to enable CORS in your REST service. As explained in MDN, once you add a custom header, the http protocol specifies performing a preflight,
Preflighted requests
Unlike simple requests (discussed above), "preflighted" requests first
send an HTTP request by the OPTIONS method to the resource on the
other domain, in order to determine whether the actual request is safe
to send. Cross-site requests are preflighted like this since they may
have implications to user data. In particular, a request is
preflighted if:
It uses methods other than GET, HEAD or POST. Also, if POST is used
to send request data with a Content-Type other than
application/x-www-form-urlencoded, multipart/form-data, or text/plain,
e.g. if the POST request sends an XML payload to the server using
application/xml or text/xml, then the request is preflighted. It sets
custom headers in the request (e.g. the request uses a header such as
X-PINGOTHER)
Addition to enabling CORS you also need to add a Access-Control-Allow-Headers header tag to accept your custom header (for the OPTIONS response). This is visible in the MDN Example,
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER
Access-Control-Max-Age: 1728000
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
UPDATE
As mentioned in the comments, the OPTION response's Access-Control-Allow-Headers is missing the last "s".
fetch data from server returns me json data as a string datatype rather than as application/json datatype, as a result the collection does not get refreshed.
I have tried giving the jquery.ajax option contentType:"application/json" to the fetch options, but still does not work.
how can i make it work? do i send a mimetype from the server? if so, how?
i am using json_encode on the data sent.
preloader.fetch({
contentType:'application/json'
});
preloader is an instance of my collection.
edit:
my template for a subview was not getting detected as i had kept it out of the masterview's $el element, corrected it, and now i am getting underscore.js error, that
str is null in
str.replace(/\\/g, '\\\\') //at line 913
is this because the backbone app is not taking it as a json object?
Request headers
Connection close
Content-Type text/html
Date Thu, 12 Apr 2012 13:00:58 GMT
Server Apache
Transfer-Encoding chunked
Vary Accept-Encoding
Response headers
has the line
Accept application/json, text/javascript, */*; q=0.01
means it is a json, then what is the problem?
I think the contentType option is for the request (your request).
Try dataType:"json".