7

I'm working on a CMS driven website using Angular (1.2 RC2) on the client side. As the content comes from a CMS I'm unfortunately forced to use receive HTML in JSON Strings. Most of the HTML gets injected and rendered just fine, but image tags have their src stripped away. From what I've read the src should have been prefixed with "unsafe:", not removed entirely if this was a XSS security issue in Angular .. but I might be mistaken.

I've been at this problem for a while now and feel I've tried everything from plausible to downright stupid. Whitelisting my CMS url, whitelisting everything, disabling $sce, forcing html trough $sce.trustAsHtml() and well ... So far, no luck. As the site is CMS driven I'm unfortunatly not able to create a plunker / fiddle, but I'm hoping a kind soul will try to help out anyway.

Update: Forgot to mention that I've also tried ng-src, same effect. Update II: If I use a HTTPS the src attribute remains and the image is shown. This is acceptable as it will run on HTTPS in production, but I still would like to know why disabling $sce doesn't work.

HTML as seen by the browser (content of slide.body)

<div class="row">
    <div class="col-md-6 visible-md visible-lg">
        <img alt="none" class="img-responsive">
    </div>
    <div class="col-xs-12 col-md-6">
        <div class="itx-article-header">
             <h1>Sulvat Quis 1</h1>

             <h2>– Nullam dictum ac enim</h2>

        </div>
        <p>Proin quis justo vel felis varius sodales sit amet ut diam.</p>
    </div>
</div>

JADE (HTML)

.my-carousel(ng-controller='CarouselCtrl')
    carousel(interval='myInterval')
        slide(ng-repeat='slide in slides', active='slide.active')
            .my-carousel-item(ng-bind-html='slide.body')

Angular Controller

.controller('CarouselCtrl', ['$sce', 'Article', '$scope',
    function($sce, Article, $scope) {
        $scope.myInterval = -1;
        $scope.slides = Article.query(
            {category: 'carousel'},
            function(data){
                for (var i = 0; i < data.length; i++) {
                    $scope.slides[i].body = $sce.trustAsHtml(data[i].body);                     
                }
            },
            function() {
                // Fail
            });
    }])

Example JSON Response (Slide) Sorry about the wide box, didn't manage to format it.

{
"cmarId": 16,
"corp": {
    "corpId": 2,
    "guiSelected": false
},
"createdAt": "Sep 27, 2013",
"articleTag": "slide-1",
"headline": "Slide 1",
"highlighted": false,
  "body": "\u003cdiv class\u003d\"container my-carousel-container-small\"\u003e\r\n\u003cdiv class\u003d\"row\"\u003e\r\n\u003cdiv class\u003d\"col-md-6 visible-md visible-lg\"\u003e\u003cimg ng-src\u003d\"img/illustrative/laptop.jpg\" alt\u003d\"none\" class\u003d\"img-responsive\" /\u003e\u003c/div\u003e\r\n\u003cdiv class\u003d\"col-xs-12 col-md-6\"\u003e\r\n\u003cdiv class\u003d\"my-article-header\"\u003e\r\n\u003ch1\u003eSulvat Quis 1\u003c/h1\u003e\r\n\u003ch2\u003e– Nullam dictum ac enim\u003c/h2\u003e\r\n\u003c/div\u003e\r\n\u003cp\u003eProin quis justo vel felis varius sodales sit amet ut diam. Fusce auctor sapien nec purus sagittis, in venenatis turpis luctus. Nullam dictum ac enim sed commodo. Vivamus et placerat sapien.\u003c/p\u003e\r\n\u003c/div\u003e\r\n\u003c/div\u003e\r\n\u003c/div\u003e",
"articlePriority": 0,
"category": {
    "cmcaId": 9,
    "corp": {
        "corpId": 2,
        "guiSelected": false
    },
    "name": "carousel",
    "visibleInMenu": false
},
"published": true

}

Index
  • 2,351
  • 3
  • 33
  • 50
  • Did you try `ng-src`? – kubuntu Sep 27 '13 at 09:28
  • Can you show us what you have done with `$sce.trustAsHtml()`? – Ye Liu Sep 27 '13 at 14:25
  • 1
    Digging into the source, it appears that `$sce.trustAsHtml()` uses the `$sanitize` service which strips out all uri attributes (`background`, `cite`, `href`, `longdesc`, `src`, and `usemap`) – Justin Lovero Oct 27 '13 at 08:51
  • Right, that sort of makes sense. But isn't the description a bit misleading? If you explicit trust something, why does it get sanitized? Also, do you have any clue as to why disabling $sce didn't work? – Index Oct 27 '13 at 22:08
  • Waiting to see which answer the community feels is more correct - no up votes yet - if nothing is up-voted I will still award the bounty... – PW Kad Oct 30 '13 at 14:57
  • @kg if there is an answer that works for you feel free to mark it as such and I will award bounty – PW Kad Nov 02 '13 at 13:39
  • @PWKad Unfortunately I haven't gotten around to testing them yet. As this was a about to go into production when I originally created the question I had to do a rollback to ng 1.0.8. I'll get to testing the solutions tomorrow morning, hopefully within the bounty grace period. – Index Nov 03 '13 at 00:46

4 Answers4

9

The piece you're missing here is trusting the image url via $sce.trustAsResourceURL();. See here for the relevant documentation.

EDIT: Also, it appears you're not wrapping the ng-src value in quotes (as well as the required quotes for it being an HTML attribute). That won't work--ng-src expects a javascript string as the final result of the expression, and you're providing it with an invalid javascript literal.

Jeff Hubbard
  • 9,822
  • 3
  • 30
  • 28
  • As far I as I can see you're supposed to send only the image url into the above mentioned method. I'm working with an entire String of HTML which I have no way of knowing if/where the image is. In regards to your update, where are there missing quotes? – Index Nov 01 '13 at 19:55
  • Unless I've horribly misread the html blob, ng-src\u003d\"img/illustrative/laptop.jpg\" has only the outer double-quotes. – Jeff Hubbard Nov 01 '13 at 19:58
  • As for the url, you're kinda stuck there. You can either disable sanitization (not recommended), or you can restructure the response to give you the list of urls you need to trust for various reasons (resource vs. link). – Jeff Hubbard Nov 01 '13 at 22:18
  • Heh, maybe I'm reading it all wrong too .. but I see both double quotes. In regards to your suggestion that isn't possible as the HTML comes from a CMS where user add content using a Rich / Wysiwyg editor. – Index Nov 02 '13 at 11:34
  • I see a single set of escaped double quotes wrapping the url with an encoded = on the left beside the ng-src attribute. What I expected to see was a set of escaped double quotes next to a set of escaped single quotes surrounding the url (i.e. "'img/illustrative/laptop.jpg'"). The important difference here is that one is an invalid javascript expression (img/illustrative/laptop.jpg), the other is a valid javascript string('img/illustrative/laptop.jpg'). Ignore the double quotes when dealing with directives that take an expression. – Jeff Hubbard Nov 02 '13 at 16:39
  • Aha, didn't quite get that at first. Thanks for the explanation. – Index Nov 03 '13 at 00:42
  • Accept as the answer is correct (I made a small demo which worked), even though it unfortunately was unable to help me. – Index Nov 05 '13 at 09:34
2

Reference: The AngularJS documentation for $sanitize has a demo. In this demo, if you use src instead of ng-src and ng-bind-html to a function (which returns an object from $sce.trustAsHtml()), the image appears as expected.


Suggested edits: I would first replace ng-src with src.

Then I would change

for (var i = 0; i < data.length; i++) {
    $scope.slides[i].body = $sce.trustAsHtml(data[i].body);                     
}

to

for (var i = 0; i < data.length; i++) {
    $scope.slides[i].body = function() {
        return $sce.trustAsHtml(data[i].body);
    };
}

Finally, this

.my-carousel-item(ng-bind-html='slide.body')

would change to

.my-carousel-item(ng-bind-html='slide.body()')

I have not tested this myself, but please let me know if this works.


Edit (2013-11-02): Typo fix — I added return to the function example. The code should work as expected now.

Tyler Eich
  • 4,239
  • 3
  • 21
  • 45
  • Your answer of not using the Angular bindings doesn't seem to be going the correct route. Why would you forego the bindings to bypass the feature? – PW Kad Oct 27 '13 at 22:44
  • I honestly don't know. I pasted `none` into the example and it worked. As soon as I changed `src` to `ng-src`, the image falls back to alternate text. – Tyler Eich Oct 27 '13 at 22:56
  • @TylerEich: If you read the update (which was posted before your reply) you'll see that I have indeed tried both src and ng-src, no difference. So I'm thinking that your way of bypassing the bindings is what makes it work. – Index Oct 28 '13 at 09:41
  • What binding is bypassed? There is no point in using ng:src if you are just setting ready to use HTML code. – Juliane Holzt Oct 29 '13 at 19:55
2

See this comment on the AngularUI discussion: https://github.com/angular-ui/bootstrap/issues/813#issuecomment-25760432

It is a directive which reimplements the old bind-html-unsafe feature which was present in AngularJS pre 1.2. In the end this will do the same as suggested above but with a minimum of hassle. So you could just use ng-bind-html-unsafe and not make your code less readable by having to add the $sce bypass in your main code.

See also this Stackoverflow question: How do you use $sce.trustAsHtml(string) to replicate ng-bind-html-unsafe in Angular 1.2+

Community
  • 1
  • 1
Juliane Holzt
  • 2,135
  • 15
  • 14
2

After hours of searching how to display image uploaded via WYSIWYG editor for my CMS app i found this

.filter('toTrusted', ['$sce', function($sce) {
    return function(text) {
        return $sce.trustAsHtml(text);
    };
}]);
<div ng-bind-html="data  | toTrusted"></div>

Hope it helps

ionyekanna
  • 440
  • 5
  • 7