Bläddra i källkod

Tighten up security around unfurls

Dont render OGP metadata:
- for URLs not actually found in the original message
- from MUC participants (only from the MUC itself)
JC Brand 4 år sedan
förälder
incheckning
426bf1d631
4 ändrade filer med 134 tillägg och 34 borttagningar
  1. 1 1
      spec/chatbox.js
  2. 93 1
      spec/unfurls.js
  3. 0 32
      src/headless/plugins/chat/model.js
  4. 40 0
      src/headless/plugins/muc/muc.js

+ 1 - 1
spec/chatbox.js

@@ -911,7 +911,7 @@ describe("Chatboxes", function () {
 
     describe("Special Messages", function () {
 
-        fit("'/clear' can be used to clear messages in a conversation",
+        it("'/clear' can be used to clear messages in a conversation",
                 mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
 
             await mock.waitForRoster(_converse, 'current');

+ 93 - 1
spec/unfurls.js

@@ -89,7 +89,7 @@ describe("A Groupchat Message", function () {
         metadata_stanza = u.toStanza(`
             <message xmlns="jabber:client" from="${muc_jid}" to="${_converse.jid}" type="groupchat">
                 <apply-to xmlns="urn:xmpp:fasten:0" id="eda6c790-b4f3-4c07-b5e2-13fff99e6c04">
-                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:url" content="https://duckduckgo.com/" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:url" content="https://duckduckgo.com" />
                     <meta xmlns="http://www.w3.org/1999/xhtml" property="og:site_name" content="DuckDuckGo" />
                     <meta xmlns="http://www.w3.org/1999/xhtml" property="og:image" content="https://duckduckgo.com/assets/logo_social-media.png" />
                     <meta xmlns="http://www.w3.org/1999/xhtml" property="og:title" content="DuckDuckGo - Privacy, simplified." />
@@ -101,4 +101,96 @@ describe("A Groupchat Message", function () {
         await u.waitUntil(() => view.querySelectorAll('converse-message-unfurl').length === 2);
         done();
     }));
+
+    it("will not render an unfurl based on a URL not in the original message", mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
+        const nick = 'romeo';
+        const muc_jid = 'lounge@montague.lit';
+        await mock.openAndEnterChatRoom(_converse, muc_jid, nick);
+        const view = _converse.api.chatviews.get(muc_jid);
+
+        const message_stanza = u.toStanza(`
+            <message xmlns="jabber:client" type="groupchat" from="${muc_jid}/arzu" xml:lang="en" to="${_converse.jid}" id="eda6c790-b4f3-4c07-b5e2-13fff99e6c04">
+                <body>https://www.youtube.com/watch?v=dQw4w9WgXcQ</body>
+                <active xmlns="http://jabber.org/protocol/chatstates"/>
+                <origin-id xmlns="urn:xmpp:sid:0" id="eda6c790-b4f3-4c07-b5e2-13fff99e6c04"/>
+                <stanza-id xmlns="urn:xmpp:sid:0" by="${muc_jid}" id="8f7613cc-27d4-40ca-9488-da25c4baf92a"/>
+                <markable xmlns="urn:xmpp:chat-markers:0"/>
+            </message>`);
+        _converse.connection._dataRecv(mock.createRequest(message_stanza));
+        const el = await u.waitUntil(() => view.querySelector('.chat-msg__text'));
+        expect(el.textContent).toBe('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
+
+        spyOn(view.model, 'handleMetadataFastening').and.callThrough();
+
+        const metadata_stanza = u.toStanza(`
+            <message xmlns="jabber:client" from="${muc_jid}" to="${_converse.jid}" type="groupchat">
+                <apply-to xmlns="urn:xmpp:fasten:0" id="eda6c790-b4f3-4c07-b5e2-13fff99e6c04">
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:site_name" content="YouTube" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:url" content="https://www.youtube.com/watch?v=tmY-G6sngk8" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:title" content="Rick Astley - Never Gonna Give You Up (Video)" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:image" content="https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:image:width" content="1280" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:image:height" content="720" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:description" content="Rick Astley&amp;#39;s official music video for &quot;Never Gonna Give You Up&quot; Listen to Rick Astley: https://RickAstley.lnk.to/_listenYD Subscribe to the official Rick Ast..." />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:type" content="video.other" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:url" content="https://www.youtube.com/embed/dQw4w9WgXcQ" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:secure_url" content="https://www.youtube.com/embed/dQw4w9WgXcQ" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:type" content="text/html" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:width" content="1280" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:height" content="720" />
+                </apply-to>
+            </message>`);
+        _converse.connection._dataRecv(mock.createRequest(metadata_stanza));
+
+        await u.waitUntil(() => view.model.handleMetadataFastening.calls.count());
+        expect(view.model.handleMetadataFastening.calls.first().returnValue).toBe(false);
+        expect(view.querySelector('converse-message-unfurl')).toBe(null);
+        done();
+    }));
+
+    it("will not render an unfurl received from a MUC participant", mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
+        const nick = 'romeo';
+        const muc_jid = 'lounge@montague.lit';
+        await mock.openAndEnterChatRoom(_converse, muc_jid, nick);
+        const view = _converse.api.chatviews.get(muc_jid);
+
+        const message_stanza = u.toStanza(`
+            <message xmlns="jabber:client" type="groupchat" from="${muc_jid}/arzu" xml:lang="en" to="${_converse.jid}" id="eda6c790-b4f3-4c07-b5e2-13fff99e6c04">
+                <body>https://www.youtube.com/watch?v=dQw4w9WgXcQ</body>
+                <active xmlns="http://jabber.org/protocol/chatstates"/>
+                <origin-id xmlns="urn:xmpp:sid:0" id="eda6c790-b4f3-4c07-b5e2-13fff99e6c04"/>
+                <stanza-id xmlns="urn:xmpp:sid:0" by="${muc_jid}" id="8f7613cc-27d4-40ca-9488-da25c4baf92a"/>
+                <markable xmlns="urn:xmpp:chat-markers:0"/>
+            </message>`);
+        _converse.connection._dataRecv(mock.createRequest(message_stanza));
+        const el = await u.waitUntil(() => view.querySelector('.chat-msg__text'));
+        expect(el.textContent).toBe('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
+
+        spyOn(view.model, 'handleMetadataFastening').and.callThrough();
+
+        const metadata_stanza = u.toStanza(`
+            <message xmlns="jabber:client" from="${muc_jid}/arzu" to="${_converse.jid}" type="groupchat">
+                <apply-to xmlns="urn:xmpp:fasten:0" id="eda6c790-b4f3-4c07-b5e2-13fff99e6c04">
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:site_name" content="YouTube" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:url" content="https://www.youtube.com/watch?v=dQw4w9WgXcQ" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:title" content="Rick Astley - Never Gonna Give You Up (Video)" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:image" content="https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:image:width" content="1280" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:image:height" content="720" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:description" content="Rick Astley&amp;#39;s official music video for &quot;Never Gonna Give You Up&quot; Listen to Rick Astley: https://RickAstley.lnk.to/_listenYD Subscribe to the official Rick Ast..." />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:type" content="video.other" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:url" content="https://www.youtube.com/embed/dQw4w9WgXcQ" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:secure_url" content="https://www.youtube.com/embed/dQw4w9WgXcQ" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:type" content="text/html" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:width" content="1280" />
+                    <meta xmlns="http://www.w3.org/1999/xhtml" property="og:video:height" content="720" />
+                </apply-to>
+            </message>`);
+        _converse.connection._dataRecv(mock.createRequest(metadata_stanza));
+
+        await u.waitUntil(() => view.model.handleMetadataFastening.calls.count());
+        expect(view.model.handleMetadataFastening.calls.first().returnValue).toBe(false);
+        expect(view.querySelector('converse-message-unfurl')).toBe(null);
+        done();
+    }));
 });

+ 0 - 32
src/headless/plugins/chat/model.js

@@ -13,24 +13,6 @@ const { Strophe, $msg } = converse.env;
 
 const u = converse.env.utils;
 
-const METADATA_ATTRIBUTES = [
-    "og:description",
-    "og:image",
-    "og:image:height",
-    "og:image:width",
-    "og:site_name",
-    "og:title",
-    "og:type",
-    "og:url",
-    "og:video:height",
-    "og:video:secure_url",
-    "og:video:tag",
-    "og:video:type",
-    "og:video:url",
-    "og:video:width"
-];
-
-
 /**
  * Represents an open/ongoing chat conversation.
  *
@@ -488,20 +470,6 @@ const ChatBox = ModelWithContact.extend({
         return false;
     },
 
-    handleMetadataFastening (attrs) {
-        if (attrs.ogp_for_id) {
-            const message = this.messages.findWhere({'origin_id': attrs.ogp_for_id});
-            if (message) {
-                const list = [...(message.get('ogp_metadata') || []), pick(attrs, METADATA_ATTRIBUTES)];
-                message.save('ogp_metadata', list);
-                return true;
-            } else {
-                return false;
-            }
-        }
-        return false;
-    },
-
     /**
      * Determines whether the passed in message attributes represent a
      * message which corrects a previously received message, or an

+ 40 - 0
src/headless/plugins/muc/muc.js

@@ -16,6 +16,23 @@ const ADMIN_COMMANDS = ['admin', 'ban', 'deop', 'destroy', 'member', 'op', 'revo
 const MODERATOR_COMMANDS = ['kick', 'mute', 'voice', 'modtools'];
 const VISITOR_COMMANDS = ['nick'];
 
+const METADATA_ATTRIBUTES = [
+    "og:description",
+    "og:image",
+    "og:image:height",
+    "og:image:width",
+    "og:site_name",
+    "og:title",
+    "og:type",
+    "og:url",
+    "og:video:height",
+    "og:video:secure_url",
+    "og:video:tag",
+    "og:video:type",
+    "og:video:url",
+    "og:video:width"
+];
+
 const ACTION_INFO_CODES = ['301', '303', '333', '307', '321', '322'];
 
 const MUCSession = Model.extend({
@@ -2104,6 +2121,29 @@ const ChatRoomMixin = {
         window.setTimeout(() => this.removeNotification(actor, state), 10000);
     },
 
+    handleMetadataFastening (attrs) {
+        if (attrs.ogp_for_id) {
+            if (attrs.from !== this.get('jid')) {
+                // For now we only allow metadata from the MUC itself and not
+                // from individual users who are deemed less trustworthy.
+                return false;
+            }
+            const message = this.messages.findWhere({'origin_id': attrs.ogp_for_id});
+            if (message) {
+                if (!attrs['og:url'] || !message.get('body').includes(attrs['og:url'])) {
+                    // For security purposes, we don't show metadata for a URL not actually
+                    // included in the original message
+                    log.warn("Not showing OGP data because we can't find the relevant URL in the original message");
+                    return false;
+                }
+                const list = [...(message.get('ogp_metadata') || []), pick(attrs, METADATA_ATTRIBUTES)];
+                message.save('ogp_metadata', list);
+                return true;
+            }
+        }
+        return false;
+    },
+
     /**
      * Handler for all MUC messages sent to this groupchat. This method
      * shouldn't be called directly, instead {@link _converse.ChatRoom#queueMessage}