I've created a script which autoloads javascript files based on the users location. Like any other loaders, I create a <script> element, set it's src attribute and add it to <head>. I then listen for onload and onreadystatechanged etc. events, then check for the presence of a value exposed by the file. This works.
The problem is, when supplied with an invalid cross origin url "http://fail_on_purpose", my ISP returns HTML (directing the browser to reload with a different address). I can catch this in the onload event, but it's too late. The HTML has already been injected on the page within the <script> tags. This then causes the browser (firefox at least) to issue a SyntaxError exception.
All I can do is remove the offending element from the DOM. But, I don't know if this is going to cause problems in other browsers, so I would prefer to not have the error at all.
What is the best way to check for a valid javascript file then inject it into the page?
Update:
Issuing a HEAD or GET request to check if the file exists/is valid, doesn't work due to the "Cross Origin" protection in the browser. I don't have access to the CDN, so that is an unfixable problem.
You could send a HEAD request and parse response headers to see if the page returns successfully, i.e. not with redirection, client error or server error response code. Any decently implemented server should set correct response code.
Ideally, you'd also check content-type to include application/javascript, if the server is set up to set it correctly.
If this matches, you know that the script exists, so you can then proceed with loading it the same as you do now.
Alternatively, you already get these headers with GET request, so you could do all that with a single request. But with incorrect request, you'll be waiting for all the payload before determining it's incorrect, which will take up more time and waste data transfer. The latter is something worth considering if you're expecting mobile users, you wouldn't want to waste data plans like that.
As Sumit suggests, you might load the script via an Ajax request, then check it somehow (e.g. because you know that it contains a particular string).
On success, just proceed as you did - append a <script> tag to the head or body element with the "src" attribute set. If caching is enabled for the URL, the browser won't do another HTTP request for that.
Using the Ajax-loaded JS code directly (by setting scriptElem.text instead of the "src" attribute) is another option, but not as good, since it is subject to CORS restrictions. See jQuery's DOMEval function, https://code.jquery.com/jquery-3.1.0.js (line 77)
Since you want to load the code from the Google servers, the "src"-attribute version is better.
Related
I have a weird network request in my page, which refers to JavaScript files, which I removed from every html file earlier. Cache is cleared and there is no single reference to be found in the source html and the JavaScript files. For fixing that and also out of general curiosity I would like to know if there is a simple way to find out where a request was triggered, preferably using the chrome-devtools.
Update:
Thanks to jaredwilli I found the initator column under the network-tab. However this only shows Other. What I would like to know, is the (html or javascript) file where those Requests have been triggered.
On the Network panel, you can determine what the initiator of a request was by viewing the Initiator column. It gives you the file, line number and type of resource it was, either Script or something else.
I have the following in the HTML body of a page:
<aside id="ADVERT">
<img src='https://www.blackstone.com/images/default-source/assets/logos/blackstone-logo.png'>
</aside>
Later in the page, there is a button that, when clicked, switches the img with another. Here is the button code:
<button onclick="switchadvert();">Switch Logo</button>
This switchadvert Javascript function is simplicity itself, just one line, using the innerHTML property:
function switchadvert()
{ document.getElementById("ADVERT").innerHTML = "<img src='http://www.redstone.com/gfx/logo.png'>";
}
This all appears to work. When I click the button, the logo changes from the Blackstone Group's logo to Redstone's logo.
My question is how does this work, and why does it work?
HOW:
Apparently, as soon as I update the DOM with this new innerHTML, the browser realizes this part of the page requires a resource it does not have (the redstone logo) and goes and fetches it? Long after the page has loaded?
WHY:
I thought that even if I went to the trouble of doing an AJAX call, to an XMLHttpRequest object, I was not allowed to go get resources from another site after the page has loaded. But here it is doing it without any calls! Doesn't this open the door for cross-site scripting abuse?
For the <img> question, and how does the browser know it has to fetch new resources, that's simply what does the HTMLImageElement when its src property is set.
It doesn't matter when this happens, and the element doesn't even have to be appended in the document; when you set this src, its fetch algorithm will kick in.
For the cross-origin question, this neither has no relation with when the request is made. But to clarify an other misconception, cross-domain restrictions are here to avoid scripts being able to access cross-domain data, but you can still very well display cross-domain data in your page. And here, since you didn't set the cross-origin attribute of your <img>, then you won't be able to access the content of this image.
TL;DR:
In today's browsers there should be no treat to XSS by loading an image
When it comes to fetching data there is a rule called CORS that comes into play.
CORS only allows certain methods with a few allowed headers without additional configuration on the server end.
When it comes to images, the browser will fetch (GET) the url, which it allows by CORS and then determine its content type, either by looking at the Content-Type header or some other processes.
Assuming the source is not affected by XSS and if it's a valid image, it will display it and if not throw an error, but the browser will never execute any javascript inside the browser so there is no threat to XSS. You can test this by having an image source set to a javascript file.
The problem:
I work on an internal tool that allows users to upload images - and then displays those images back to them and others.
It's a Java/Spring application. I have the benefit of only needing to worry about IE11 exactly and Firefox v38+ (Chrome v43+ would be a nice to have)
After first developing the feature, it seems that users can just create a text file like:
<script>alert("malicious code here!")</script>
and save it as "maliciousImage.jpg" and upload it.
Later, when that image is displayed inside image tags like:
<img src="blah?imgName=foobar" id="someImageID">
actualImage.jpg displays normally, and maliciousImage.jpg displays as a broken link - and most importantly no malicious content is interpreted!
However If the user right-clicks on this broken link, and clicks 'view image'... bad things happen.
the browser does 'content-sniffing' a concept which is new to me, detects that 'maliciousImage.jpg' is actually a text file, and very kindly renders it as HTML without hesitation. Any script tags are passed to the JavaScript interpreter and, as you can imagine, we don't want this.
What I've tried so far
In short, every possible combination of response headers I can think of to prevent the browser from content-sniffing. All the answers I've found here on stackoverflow, and other docs, imply that setting the content-type header should prevent most browsers from content-sniffing, and setting X-content options should prevent some versions of IE.
I'm setting the x-content-type-options to no sniff, and I'm setting the response content type. The docs I've read lead me to believe this should stop content-sniffing.
response.setHeader("X-Content-Type-Options", "nosniff");
response.setContentType("image/jpg");
I'm intercepting the response and these headers are present, but seem to have no effect on how the malicious content is processed...
I've also tried detecting which images are and are not malicious at the point of upload, but I'm quickly realizing this is very much non-trivial...
End goal:
Naturally - any output at all for images that aren't really images (garbled nonsense, an unhandled exception, etc) would be better than executing the text-file as HTML/javascript in the clear, but displaying any malicious HTML as escaped/CDATA'd plain-text would be ideal... though maybe a bit impractical.
So I ended up fixing this problem but forgot to answer my own question:
Step 1: blocking invalid images
To get a quick fix out, I simply added some fairly blunt code that checked if an image was actually an image - during upload and before serving it, using the imageio lib:
import javax.imageio.ImageIO;
//......
Image img = attBO.getImage(imgId);
InputStream x = new ByteArrayInputStream(img.getData());
BufferedImage s;
try {
s = ImageIO.read(x);
s.getWidth();
} catch (Exception e) {
throw new myCustomException("Invalid image");
}
Now, initially i'd hoped that would fix my problem - but in reality it wasn't that simple and just made generating a payload more difficult.
While this would block:
<script>alert("malicious code here!")</script>
It's very possible to generate a valid image that's also an XSS payload - just a little more effort....
Step 2: framework silliness
It turned out there was an entire post-processing workflow that I'd never touched, that did things such as append tokens to response bodies and use additional frameworks to decorate responses with CSS, headers, footers etc.
This meant that, although the controller was explicitly returning image/png, it was being grabbed and placed (as bytes) post processing was taking that bytestream, and wrapping it in a header and footer, to form a fully qualified 'view' - this view would always have the 'content-type' text/html and thus was never displayed correctly.
The crux of this problem was that my controller was directly returning an image, in a RESTful fashion, when the rest of the framework was built to handle controllers returning full fledged views.
So I had to step through this workflow and create exceptions for the controllers in my code that returned something other than worked in a restful fashion.
for example with with site-mesh it was just an exclude(as always, simple fix once I understood the problem...):
<decorators defaultdir="/WEB-INF/decorators">
<excludes>
<pattern>*blah.ctl*</pattern>
</excludes>
<decorator name="foo" page="myDecorator.jsp">
<pattern>*</pattern>
</decorator>
and then some other other bespoke post-invocation interceptors.
Step 3: Content negotiation
Now, I finally got the stage where only image bytecode was being served and no review was being specified or explicitly generated.
A Spring feature called 'content negotiation' kicked in. It tries to reconcile the 'accepts' header of the request, with the 'messageconverters' it has on hand to produce such responses.
Because spring by default doesn't have a messageconverter to produce image/png responses, it was falling back to text/html - and I was still seeing problems.
Now, were I using spring 4, I could've simply added the annotation:
#Produces("image/png")
to my controller - simple fix...
Step 4: Legacy dependencies
but because I only had spring 3.0.5 (and couldn't upgrade it) I had to try other things.
I tried registering new messageconverters but that was a headache or adding a new post-method interceptor to simply change the content-type back to 'image/png' - but that was a hacky headache.
In the end I just exposed the request/reponse in the controller, and wrote my image directly to the response body - circumventing Spring's content-negotiation altogether
....and finally my image was served as an image and displayed as an image - and no injected code was executed!
That sounds odd, because it works perfectly elsewhere. Are you sure the X-Content-Type-Options header is present in the responses?
Here is a demo I built a while back, where I have a file that's a valid html, gif and javascript. As you can see it first loads as an HTML, but then loads itself as an image and as a script (which executes):
http://research.insecurelabs.org/content-sniffing/gifjs.html
However if you load it using the "X-Content-Type-Options: nosniff" header, the script no longer executes:
http://research.insecurelabs.org/content-sniffing/nosniff/gifjs.html
Btw, the image renders properly in FF/IE, but not in Chrome.
Here is a demo, where I attempted what you described:
http://research.insecurelabs.org/content-sniffing/stackexchange.html
First image is without nosniff, and second is with, and it seems to work as intended. Second one does not run the script when opened with "view image".
Edit:
Firefox doesn't seem to support X-Content-Type-Options: nosniff
So, you should also add "Content-disposition: attachment;filename=image.gif" or similar to the images. The image will load normally if loaded through an image tag, but if you open the URL directly, you will force a download instead of showing the image directly in the browser.
Example: http://research.insecurelabs.org/content-sniffing/attachment/
adeneo is pretty much spot-on. You should use whatever image library you want to check if the uploaded file is a valid file for the type it claims to be. Anything the client sends can be manipulated.
In this John Resig article, he's is dealing with a dictionary-sized list of words with javascript, and he's loading the content via ajax from a CDN.
The words are loaded in with newlines separating the words. Then he says cross domain fails:
There's a problem, though: We can't load our dictionary from a CDN!
Since the CDN is located on another server (or on another sub-domain,
as is the case here) we're at the mercy of the browser's cross-origin
policy prohibiting those types of requests. All is not lost though -
with a simple tweak to the dictionary file we can load it across
domains.
First, we replace all endlines in the dictionary file with a space.
Second, we wrap the entire line with a JSONP statement. Thus the final
result looks something like this:
dictLoaded('aah aahed aahing aahs aal... zyzzyvas zzz');
This allows us to do an Ajax request for the file and have it work as
would expected it to - while still benefiting from all the caching and
compression provided by the browser.
So, if I'm reading this correctly, simply adding his method dictLoaded('original content') around the original content alone causes the ajax request to not fail.
Is that (turning it into a function + param) really all it takes? and why does JSONP solve the problem of cross domain access restriction?
the <script> tags can load any JS file from anywhere (even cross domain). The nice thing that comes with it is that the code inside that script is also executed, therefore, a method of bypassing cross-domain restrictions.
The problem is, when the code gets executed, it's executed in the global scope. so having this code:
var test = 'foo'
will create a test variable in the global scope.
To mitigate this, you use enclose the reply in a function. This is the "P" in "JSONP" which means "padding". This encloses your reply in a function call.
So if your foreign script has:
myFunction({
test : 'foo'
});
It calls myFunction and passes an object with test key which has value foo. The receiving function would look like:
function myFunction(data){
//"data.test" is "foo"
}
Now we have successfully bypassed the cross-domain restriction. The essential parts needed are:
the receiving function (which can be dynamically created and discarded after use)
the "padded" JSON reply
Is that (turning it into a function + param) really all it takes?
Yes.
and why does that solve the problem of cross domain access restriction?
You should read about JSONP. The idea is that you can now include a <script> tag dynamically pointing to the resource instead of sending an AJAX request (which is prohibited). And since you have wrapped the contents with a function name, this function will be executed and passed as argument the JSON object. So all that's left for you is to define this function.
It is because of the JSONP statement that he added.
"JSON with padding" is a complement to the base JSON data format. It provides a method to request data from a server in a different domain, something prohibited by typical web browsers because of the Same origin policy.
This works via script element injection.
JSONP makes sense only when used with a script element. For each new JSONP request, the browser must add a new element, or reuse an existing one. The former option - adding a new script element - is done via dynamic DOM manipulation, and is known as script element injection. The element is injected into the HTML DOM, with the URL of the desired JSONP endpoint set as the "src" attribute. This dynamic script element injection is usually done by a javascript helper library. jQuery and other frameworks have jsonp helper functions; there are also standalone options.
Source: http://en.wikipedia.org/wiki/JSONP
In WebKit I get the following error on my JavaScript:
Refused to execute a JavaScript script. The source code of script found within request.
The code is for a JavaScript spinner, see ASCII Art.
The code used to work OK and is still working correctly in Camino and Firefox. The error only seems to be thrown when the page is saved via a POST and then retrieved via a GET. It happens in both Chrome/Mac and Safari/Mac.
Anyone know what this means, and how to fix this?
This "feature" can be disabled by sending the non-standard HTTP header X-XSS-Protection on the affected page.
X-XSS-Protection: 0
It's a security measure to prevent XSS (cross-site scripting) attacks.
This happens when some JavaScript code is sent to the server via an HTTP POST request, and the same code comes back via the HTTP response. If Chrome detects this situation, the script is refused to run, and you get the error message Refused to execute a JavaScript script. Source code of script found within request.
Also see this blogpost about Security in Depth: New Security Features.
Short answer: refresh the page after making your initial submission of the javascript, or hit the URL that will display the page you're editing.
Long answer: because the text you filled into the form includes javascript, and the browser doesn't necessarily know that you are the source of the javascript, it is safer for the browser to assume that you are not the source of this JS, and not run it.
An example: Suppose I gave you a link your email or facebook with some javascript in it. And imagine that the javascript would message all your friends my cool link. So, the game of getting that link to be invoked becomes simply, find a place to send the javascript such that it will be included in the page.
Chrome and other WebKit browsers try to mitigate this risk by not executing any javascript that is in the response, if it was present in the request. My nefarious attack would be thwarted because your browser would never run that JS.
In your case, you're submitting it into a form field. The Post of the form field will cause a render of the page that will display the Javascript, causing the browser to worry. If your javascript is truly saved, however, hitting that same page without submitting the form will allow it to execute.
As others have said, this happens when an HTTP response contains a JavaScript and/or HTML string that was also in the request. This is usually caused by entering JS or HTML into a form field, but can also be triggered in other ways such as manually tweaking the URL's parameters.
The problem with this is that someone with bad intentions could put whatever JS they want as the value, link to that URL with the malicious JS value, and cause your users trouble.
In almost every case, this can be fixed by HTML encoding the response, though there are exceptions. For example, this will not be safe for content inside a <script> tag. Other specific cases can be handled differently - for example, injecting input into a URL is better served by URL encoding.
As Kendall Hopkins mentioned, there may be a few cases when you actually want JavaScript from form inputs to be executed, such as creating an application like JSFiddle. In those cases, I'd recommend that you you at least scrub through the input in your backend code before blindly writing it back. After that, you can use the method he mentioned to prevent the XSS blockage (at least in Chrome), but be aware that it is opening you to attackers.
I used this hacky PHP trick just after I commit to database, but before the script is rendered from my _GET request.:
if(!empty($_POST['contains_script'])) {
echo "<script>document.location='template.php';</script>";
}
This was the cheapest solution for me.