Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ckeditor5-image fetchLocalImage() rejects Base64 due to Content Security Policy violation #7957

Closed
giarcjc opened this issue Aug 27, 2020 · 8 comments · Fixed by #8707
Closed
Assignees
Labels
squad:core Issue to be handled by the Core team. type:bug This issue reports a buggy (incorrect) behavior.

Comments

@giarcjc
Copy link

giarcjc commented Aug 27, 2020

After deploying our application to our server with CK editor, I noticed that if coping and pasting a selection of text that contains an image from MS Word, the image will fail to be embedded in the editor. This is not the case when copying only text or only an image from Word, but when the selection contains both text and an embedded image, a browser alert is triggered "TypeError: Failed to fetch", and the console log shows the error to be due to a Content Security Policy violation.

Initially I believed that this must be due to a misconfiguration of our server's CSP, because it does not happen when I use the CK demo at https://ckeditor.com/docs/ckeditor5/latest/features/image-upload/image-upload.html or at https://ckeditor.com/docs/ckeditor5/latest/features/pasting/paste-from-word.html, and I could not reproduce it locally, only on our servers.

However, I have tested this by changing the CSP in my server's nginx config to exactly match the recommended CSP configuration from this page: https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/csp.html and I am still getting the error. It does not seem to be browser-related (tested in Chrome, FF and Safari).

After a lot of research and trial and error, I believe the problem is with the use of the native fetch api in fetchLocalImage() @ckeditor/ckeditor5-image/src/imageupload/utils.js when attempting to embed the placeholder image from the Base64 src. It seems to allow the promise rejection and image insertion as long as no upload adapter is configured (which might explain why it works in the demo) but with an upload adapter configured a blocking browser alert pops up and the image is not embedded.

By adding a few lines of code to the fetchLocalImage fn to effectively mimic the server's CSP, I have been able to reproduce this issue using a local build.

Simplified steps to reproduce (edited by Mgsy)

  1. Add CSP meta tag to the page:
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; connect-src 'self' http://*.cke-cs.com; script-src 'self'; img-src * data:; style-src 'self' 'unsafe-inline'; frame-src *">
  1. Copy some text and an image from MS Word.
  2. Paste it to the editor.

Original steps to reproduce:

  1. Go to https://ckeditor.com/ckeditor-5/online-builder/ (any editor type) and use all defaults to build and download a CK editor
  2. install dependencies,
  3. run the build script ('yarn build' or 'npm build')
  4. open /sample/index.html in browser (Chrome, FF or Safari)
  5. Copy the contents of this Word doc: TextAndImgForPasteTest.docx and paste into the editor. The text and image are both inserted into the editor.
  6. Now, to mimic the server's CSP add the following code to the top of the fetchLocalImage() function in node_modules/@ckeditor/ckeditor5-image/src/imageupload/utils.js (starting on line 37, before the return statement) and save file:
	const CSP = "default-src 'none'; connect-src 'self'; script-src 'self'; img-src * data:; style-src 'self' 'unsafe-inline'; frame-src *"
	const cspMetaTag = document.createElement('meta');
	cspMetaTag.setAttribute('http-equiv', 'Content-Security-Policy');
	cspMetaTag.setAttribute('content', CSP);
	document.querySelector('head').appendChild(cspMetaTag);

Modified function now looks like this:

export function fetchLocalImage( image ) {
		// Use CK-recommended CSP from https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/csp.html
		const CSP = "default-src 'none'; connect-src 'self'; script-src 'self'; img-src * data:; style-src 'self' 'unsafe-inline'; frame-src *"
		const cspMetaTag = document.createElement('meta');
		cspMetaTag.setAttribute('http-equiv', 'Content-Security-Policy');
		cspMetaTag.setAttribute('content', CSP);
		document.querySelector('head').appendChild(cspMetaTag);
	
	return new Promise( ( resolve, reject ) => {
		const imageSrc = image.getAttribute( 'src' );

		// Fetch works asynchronously and so does not block browser UI when processing data.
		fetch( imageSrc )
			.then( resource => resource.blob() )
			.then( blob => {
				const mimeType = getImageMimeType( blob, imageSrc );
				const ext = mimeType.replace( 'image/', '' );
				const filename = `image.${ ext }`;
				const file = new File( [ blob ], filename, { type: mimeType } );

				resolve( file );
			} )
			.catch( reject );
	} );
}
  1. repeat steps 3-5.
  2. The text and image are still pasted, but the Browser console log will show following errors:
utils.js:48 Refused to connect to 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxMTEhUTExIWFRUXGRcXFxcYFRgYGhoVGBgWGBgYFxcYHSggGxolGxcYITEhJSktLy4vGB8zODMsNygtLisBCgoKDg0OGhAQGy0lICUtLS0tLS8tLS8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLf/AABEIAMIBAwMBIgACEQEDEQH/xAAcAAABBQEBAQAAAAAAAAAAAAADAAIEBQYBBwj/xAA9EAABAgQEBAMGAwcEAwEAAAABAhEAAyExBBJBUQUiYXETgZEGMqGxwfAjQtEHFFJiguHxFTNykjSisrP/xAAZAQADAQEBAAAAAAAAAAAAAAAAAQIDBAX/xAAlEQACAgEEAgICAwAAAAAAAAAAAQIRAxIhMUEEURNhMqFCcfD/2gAMAwEAAhEDEQA/ADkmOZYKEx0Jj2zyQLQssGyRzJCs...eH42bJlzlSpi5aiZSSUKKSUkqdJKTagpE/2Px82ZicsyatacqqKWpQqUvQmFCglwRHlGxnYSWZhdCT710gxY/uktJllMtIOchwkCnhkt61hQoxNw+L/KNKf/QjHyVnMam+/RUKFES5Exyv9tR1zj5Rayxyf0p+YhQoEMCpI2upj2cUjpQGUWD0q3UR2FABVKUWJ+7xxdWeFCjPsz7C6gabaaRF4YkGXMJvy/8A6o/UwoUEQ7JKx+IkaPbSytI1eGkpzJVlDlSgSwdgtQAfsAPKFCjo6Q0Q/aZREjEEFiBQi4qbHSw9INgiyEtqgHzykv3esKFDfBXZGxRdFa+7fqAfnEjiiQUocP8Ahqv3P6D0EKFB2iWVkv8A2lnbKR0ObTaCezRfw3rTXuP1hQovoXZO4aXVWrJo/cRHnIH7xKLByGJapABIfzhQol9jA8VGWecvLRJpSr9IuDzIlFXMQiYQTWoUkA12BPrChRjL+P8AuhFZnIsTrrChQo6RH//Z' because it violates the following Content Security Policy directive: "connect-src 'self'".

Uncaught (in promise) TypeError: Failed to fetch
    at utils.js:48
    at new Promise (<anonymous>)
    at Cm (utils.js:44)
    at imageuploadediting.js:126
    at Array.map (<anonymous>)
    at Au.<anonymous> (imageuploadediting.js:126)
    at Au.fire (emittermixin.js:209)
    at oo.listenTo.priority (clipboard.js:80)
    at oo.fire (emittermixin.js:209)
    at oo.n (clipboardobserver.js:51)
  1. Now configure an upload adapter. (For convenience the Simple Adapter will do, although I've reproduced this with a custom adapter also).
    9a. In src/ckeditor.js, add import SimpleUploadAdapter from '@ckeditor/ckeditor5-upload/src/adapters/simpleuploadadapter'; and add SimpleUploadAdapter to the Editor.builtinPlugins array.

9b. Get a valid Bearer token for the server you upload to (I'm using the server/token from the Ck upload demo at https://ckeditor.com/docs/ckeditor5/latest/features/image-upload/image-upload.html), and assign to a 'token' variable.

9c. Modify sample/index.html by inserting the simple upload adapter config:

    simpleUpload: {
                // The URL that the images are uploaded to.
                uploadUrl: 'https://33333.cke-cs.com/easyimage/upload/',   <-- your server, or cke's in this case

                // Enable the XMLHttpRequest.withCredentials property.
                withCredentials: true,

                // Headers sent along with the XMLHttpRequest to the upload server.
                headers: {
                    'X-CSRF-TOKEN': 'CSFR-Token',
                    Authorization: `${token}`,   <-- some valid token, ck demo token works 
                }
            },

So the sample/index.html script should look like this (some code truncated for brevity)

<script>

         const token = 'eyJhbGciOiJIU...';

    	ClassicEditor.create( document.querySelector( '.editor' ), {
				
    				toolbar: {
    					items: [
    						...
    					]
    				},
                    simpleUpload: {
                                // The URL that the images are uploaded to.
                                uploadUrl: 'https://33333.cke-cs.com/easyimage/upload/',   <-- your server, or cke's in this case

                                // Enable the XMLHttpRequest.withCredentials property.
                                withCredentials: true,

                                // Headers sent along with the XMLHttpRequest to the upload server.
                                headers: {
                                    'X-CSRF-TOKEN': 'CSFR-Token',
                                    Authorization: `${token}`,   <-- some valid token, ck demo token works 
                                }
                            },
    				language: 'en',
    				image: {
    					...
    				},
    				table: {
    					...
    				},

                    ... etc.
    	</script>
  1. repeat steps 3-5
  2. this time nothing is inserted because a browser alert pops up (blocking all events until dismissed) reading "TypeError: Failed to fetch", in addition to two errors in the console log:
utils.js:48 Refused to connect to 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxMTEhUTExIWFRUXGRcXFxcYFRgYGhoVGBgWGBgYFxcYHSggGxolGxcYITEhJSktLy4vGB8zODMsNygtLisBCgoKDg0OGhAQGy0lICUtLS0tLS8tLS8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLf/AABEIAMIBAwMBIgACEQEDEQH/xAAcAAABBQEBAQAAAAAAAAAAAAADAAIEBQYBBwj/xAA9EAABAgQEBAMGAwcEAwEAAAABAhEAAyExBBJBUQUiYXETgZEGMqGxwfAjQtEHFFJiguHxFTNykjSisrP/xAAZAQADAQEBAAAAAAAAAAAAAAAAAQIDBAX/xAAlEQACAgEEAgICAwAAAAAAAAAAAQIRAxIhMUEEURNhMqFCcfD/2gAMAwEAAhEDEQA/ADkmOZYKEx0Jj2zyQLQssGyRzJCs...eH42bJlzlSpi5aiZSSUKKSUkqdJKTagpE/2Px82ZicsyatacqqKWpQqUvQmFCglwRHlGxnYSWZhdCT710gxY/uktJllMtIOchwkCnhkt61hQoxNw+L/KNKf/QjHyVnMam+/RUKFES5Exyv9tR1zj5Rayxyf0p+YhQoEMCpI2upj2cUjpQGUWD0q3UR2FABVKUWJ+7xxdWeFCjPsz7C6gabaaRF4YkGXMJvy/8A6o/UwoUEQ7JKx+IkaPbSytI1eGkpzJVlDlSgSwdgtQAfsAPKFCjo6Q0Q/aZREjEEFiBQi4qbHSw9INgiyEtqgHzykv3esKFDfBXZGxRdFa+7fqAfnEjiiQUocP8Ahqv3P6D0EKFB2iWVkv8A2lnbKR0ObTaCezRfw3rTXuP1hQovoXZO4aXVWrJo/cRHnIH7xKLByGJapABIfzhQol9jA8VGWecvLRJpSr9IuDzIlFXMQiYQTWoUkA12BPrChRjL+P8AuhFZnIsTrrChQo6RH//Z' because it violates the following Content Security Policy directive: "connect-src 'self'".

utils.js:48 Refused to connect to 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxMTEhUTExIWFRUXGRcXFxcYFRgYGhoVGBgWGBgYFxcYHSggGxolGxcYITEhJSktLy4vGB8zODMsNygtLisBCgoKDg0OGhAQGy0lICUtLS0tLS8tLS8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLf/AABEIAMIBAwMBIgACEQEDEQH/xAAcAAABBQEBAQAAAAAAAAAAAAADAAIEBQYBBwj/xAA9EAABAgQEBAMGAwcEAwEAAAABAhEAAyExBBJBUQUiYXETgZEGMqGxwfAjQtEHFFJiguHxFTNykjSisrP/xAAZAQADAQEBAAAAAAAAAAAAAAAAAQIDBAX/xAAlEQACAgEEAgICAwAAAAAAAAAAAQIRAxIhMUEEURNhMqFCcfD/2gAMAwEAAhEDEQA/ADkmOZYKEx0Jj2zyQLQssGyRzJCs...eH42bJlzlSpi5aiZSSUKKSUkqdJKTagpE/2Px82ZicsyatacqqKWpQqUvQmFCglwRHlGxnYSWZhdCT710gxY/uktJllMtIOchwkCnhkt61hQoxNw+L/KNKf/QjHyVnMam+/RUKFES5Exyv9tR1zj5Rayxyf0p+YhQoEMCpI2upj2cUjpQGUWD0q3UR2FABVKUWJ+7xxdWeFCjPsz7C6gabaaRF4YkGXMJvy/8A6o/UwoUEQ7JKx+IkaPbSytI1eGkpzJVlDlSgSwdgtQAfsAPKFCjo6Q0Q/aZREjEEFiBQi4qbHSw9INgiyEtqgHzykv3esKFDfBXZGxRdFa+7fqAfnEjiiQUocP8Ahqv3P6D0EKFB2iWVkv8A2lnbKR0ObTaCezRfw3rTXuP1hQovoXZO4aXVWrJo/cRHnIH7xKLByGJapABIfzhQol9jA8VGWecvLRJpSr9IuDzIlFXMQiYQTWoUkA12BPrChRjL+P8AuhFZnIsTrrChQo6RH//Z' because it violates the document's Content Security Policy.
  1. After dismissing the browser alert, the text is inserted but the image is not.

FYI: When testing this against our servers with a custom-build upload adapter, I observed that even though the image is not embedded in the editor, the request to upload the image to our server succeeds (a link to the image location on our server is returned). Perhaps some error handling could be introduced to fall back to a stock placeholder SVG in the event that the Base64 image representation fails, so as not to block the eventual resolution of the final image url from the server?

✔️ Expected result

Both the image and the text from a Word doc can be copy/pasted and embedded in the editor without errors when using an Upload Adapter and the recommended Content Security Policy.

❌ Actual result

A browser alert warns of "TypeError: Failed to fetch" and the logs show the reason to be due to CSP violations on the attempt to fetch the Base64 image representation, and the image is not embedded into the editor (but the text still is).

📃 Other details

Word for Mac version: 16.40

  • Browser: I don't think this is a browser issue, as I'm seeing it in Chrome, Firefox and Safari.

Chrome v84.0.4147.125
FireFox: 79.0 (64-bit)
Safari: 13.1.2 (15609.3.5.1.3)

Seems similar to these related issues:

#2493
#1710
#7915


If you'd like to see this fixed sooner, add a 👍 reaction to this post.

@giarcjc giarcjc added the type:bug This issue reports a buggy (incorrect) behavior. label Aug 27, 2020
@julie0842
Copy link

Hi all, just curious if anyone has any input on this issue? Any workarounds or assistance is greatly appreciated.

@Mgsy
Copy link
Member

Mgsy commented Oct 2, 2020

Hi, I'm sorry for the late response, for some reason the issue slipped out our attention.

In the mentioned Content Security Policy rules, connect-src is defined only to 'self', it seems to fail when an image has provided src in base64. As a workaround, adding data: to connect-src rule should fix the issue (however, it's not recommended solution, as it affects security).

@Mgsy Mgsy added the pending:feedback This issue is blocked by necessary feedback. label Oct 2, 2020
@giarcjc
Copy link
Author

giarcjc commented Oct 2, 2020

Thanks for the reply. I was under the impression that using 'data' with connect-src was insecure. On https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src it says "This is insecure; an attacker can also inject arbitrary data: URIs. Use this sparingly and definitely not for scripts." Obviously in my example the CSP is local to the file so it would be less of a concern, but if I were to add 'data' to the CSP on our production server nginx config wouldn't that open us up to scripting attacks?

@Reinmar
Copy link
Member

Reinmar commented Oct 5, 2020

First of all, thank you @giarcjc for such an amazing bug report ❤️

As for the issue itself, IDK if someone dig into that, but I'd be good to understand where in utils.js (and what utils actually) is that error thrown. Maybe there's something we'd be able to do about this, but first, we need to understand what makes this error pops out.

@alex-code
Copy link

alex-code commented Oct 22, 2020

I've recently implemented a CSP and encountered the same issue.
I'd prefer not to add * to connect-src for security if possible.

Would it be possible to not use fetch when processing local images as this triggers the CSP.
https://github.com/ckeditor/ckeditor5/blob/master/packages/ckeditor5-image/src/imageupload/utils.js#L41

Further
I was trying to copy an image from Microsoft Teams which failed.
Copying the image from Teams to Outlook then doing a copy from there pasted successfully into CKEditor.
Pasting an image in from the Snipping Tool also works as expected.

@ssamoojh
Copy link

ssamoojh commented Nov 5, 2020

Folks, any progress? The CSP solution is a no-go, given the security implications.

@LangQian
Copy link

LangQian commented Nov 5, 2020

There is one more scenario for this issue:

  1. Copy an image from Word along with something else (texts or other images).
  2. Paste the content into CKEditor.
  3. Error: The image is now converted into Base64 format and hence rejected by CSP.

This will be very inconvenient for users who want to copy content from Word. In reality, I believe no one would copy/paste thing from Word one by one.

Again, like other users above, adding data: to connect-src is not an option for us as well. Hope this can be fixed soon (or maybe other workaround that won't raise security issue). Thanks!

@Mgsy Mgsy added squad:core Issue to be handled by the Core team. and removed pending:feedback This issue is blocked by necessary feedback. labels Nov 23, 2020
@Mgsy Mgsy added this to the nice-to-have milestone Nov 23, 2020
@Reinmar
Copy link
Member

Reinmar commented Nov 23, 2020

TODO:

  • Does it work in CKE4? If so, what do they do differently? Talk to @f1ames.
  • If not, can we do something different in our code to not violate the CSP rules
  • If not, let's at least document this somewhere.

@psmyrek psmyrek self-assigned this Dec 21, 2020
@AnnaTomanek AnnaTomanek modified the milestones: nice-to-have, iteration 39 Dec 28, 2020
niegowski added a commit that referenced this issue Jan 12, 2021
Fix (image): Allow pasting an image with data URL scheme in src, if strict CSP rules are defined. Closes #7957.
oleq added a commit that referenced this issue Jan 14, 2021
…rict CSP rules because the latter works one way only (once set, it cannot be reverted) and that affects all tests in other packages that will follow (see #7957).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
squad:core Issue to be handled by the Core team. type:bug This issue reports a buggy (incorrect) behavior.
Projects
None yet
9 participants