<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/_stylesheets/atom_stylesheet.xsl"?>
<!DOCTYPE entry [
    <!ENTITY TAB "&lt;TAB&gt;">
    <!ENTITY CTAB "<code>&lt;TAB&gt;</code>">
    <!ENTITY CHAN "<code>#chan</code>">
    <!ENTITY CHANMOO "<code>#chan&lt;TAB&gt;:moo</code>">
]>
<entry
        xmlns="http://www.w3.org/2005/Atom"
        xmlns:pktz="https://pktz.fr/schema/"
>
    <title>Invalid command tokenization in node-irc / matrix-appservice-irc</title>
    <id>https://pktz.fr/matrix/security/2022-appservice-irc-tokenization/</id>
    <published>2022-09-13</published>
    <updated>2022-09-13</updated>
    <author>
        <name>Val Lorentz</name>
        <uri>https://valentin-lorentz.fr/</uri>
    </author>
    <pktz:keywords>
        Matrix, matrix-appservice-irc, node-irc, NVT#1534420, GHSA-xvqg-mv25-rwvw, CVE-2022-39203
    </pktz:keywords>
    <content type="xhtml" xml:lang="en">
        <div xmlns="http://www.w3.org/1999/xhtml">
            <pktz:toc />

            <section>
            <h2 id="description">Description</h2>

            <section>
            <h3 id="summary">Summary</h3>

<p>
Because of a bug in <a href="https://github.com/matrix-org/node-irc/">matrix-org/node-irc</a>, matrix-appservice-irc could be abused to mute room and make private rooms publicly accessible.
</p>

            </section>

            <section>
            <h3 id="background">Background</h3>

<p>
The IRC protocol is based on lines, delimited by <code>&lt;CRLF&gt;</code> (aka <code>\r\n</code>); and parameters within a line are delimited by a space character. When a parameter starts with a colon (<code>:</code>), it means the rest of the line is part of that parameter; that parameter is called the trailing.
<br />
Within a parameter, any character other than null characters, carriage returns, line feeds, and spaces are allowed.
</p>

<p>
Here is the grammar used in <a href="https://modern.ircdocs.horse/#parameters">the current specification</a>:

<pre>
<![CDATA[
  parameter       ::=  *( SPACE middle ) [ SPACE ":" trailing ]
  nospcrlfcl      ::=  <sequence of any characters except NUL, CR, LF, colon (`:`) and SPACE>
  middle          ::=  nospcrlfcl *( ":" / nospcrlfcl )
  trailing        ::=  *( ":" / " " / nospcrlfcl )
]]>
</pre>
</p>

<p>
The specification contains a few examples of IRC messages, and how they should be tokenized in JSON-like syntax:

<pre>
<![CDATA[
  CAP * LIST :                      ->  ["*", "LIST", ""]

  CAP * LS :multi-prefix sasl       ->  ["*", "LS", "multi-prefix sasl"]

  CAP REQ :sasl message-tags foo    ->  ["REQ", "sasl message-tags foo"]

  PRIVMSG #chan :Hey!               ->  ["#chan", "Hey!"]

  PRIVMSG #chan Hey!                ->  ["#chan", "Hey!"]

  PRIVMSG #chan ::-)                ->  ["#chan", ":-)"]
]]>
</pre>

Note that when received by clients, these commands would be prefixed by a "source" (which can be seen in the spec), but this is not relevant here.
</p>
            </section>

            <section>
            <h3 id="bug">The bug</h3>

<p>
matrix-appservice-irc uses a fork of the node-irc library, which tokenizes the trailing parameter by detecting a colon preceded by any whitespace (such as &CTAB;, the tabulation character).
</p>

<p>
This mismatch means that a parameter containing a non-space whitespace followed by a colon is parsed by node-irc as being partly the start of the trailing.
</p>

<p>
For example, this IRC line:

<pre>
<![CDATA[
  PRIVMSG #chan<TAB>:moo :Hey!
]]>
</pre>

would be parsed as being a <a href="https://modern.ircdocs.horse/#privmsg-message">PRIVMSG command</a> with these parameters

<pre>
<![CDATA[
  ["#chan", "moo :Hey!"]
]]>
</pre>

instead of:

<pre>
<![CDATA[
  ["#chan<TAB>:moo", "Hey!"]
]]>
</pre>
</p>

<p>
        This means someone (whether they are in &CHAN; or not) could make matrix-appservice-irc believe they sent messages to &CHAN;, by sending messages to &CHANMOO;, assuming Matrix puppets of the same bridge are both in &CHAN; and &CTAB;
</p>
            </section>
            </section>

            <section>
            <h2 id="exploit">Exploiting this to change room state</h2>

            <section>
            <h3 id="exploit-basics">Basics</h3>

<p>
On IRC, channel state is changed via <a href="https://modern.ircdocs.horse/#mode-message">the <code>MODE</code> command</a>, which has this syntax:

<pre>
<![CDATA[
  MODE <target> [<modestring> [<mode arguments>...]]
]]>
</pre>

    where <code>&lt;target&gt;</code> is (in the examples below) a channel, <code>&lt;modestring&gt;</code> is a list of characters, and <code>&lt;mode arguments&gt;</code> are parameters of some of the characters in the <code>&lt;modestring&gt;</code> in order (akin to C's <code>printf</code> function). It is tricky to parse, and was the source of <a href="../2022-appservice-irc-mode-parameter-confusion/">a minor vulnerability in node-irc / matrix-appservice-irc</a>; though this is irrelevant here.
</p>

<p>
As an aside: yes, the <code>MODE</code> command is tricky to use and parse.
I authored <a href="https://github.com/ircv3/ircv3-specifications/pull/484">the draft <code>named-modes</code> IRCv3 specification</a>, a work-in-progress to address both the mode character/parameter matching issue, and the ambiguity of mode characters.
You can help by supporting its implementation in your favorite clients and servers.
</p>

<p>
IRC servers validate the content of the <code>&lt;modestring&gt;</code> to exclude characters they do not understand, which means &CTAB; and colons cannot be used there.
I also could not find a way to use it in a <code>&lt;mode arguments&gt;</code> in any interesting way. This left <code>&lt;target&gt;</code>.
</p>

<p>
Unsurpringly, neither Matrix nor most IRC servers like &CTAB; in room/channel names. However, some IRC servers do allow it, including:

    <ul>
        <li><a href="https://inspircd.org/">InspIRCd</a></li>
        <li><a href="https://www.ircd-hybrid.org/">ircd-hybrid</a> and most of its forks (including <a href="https://github.com/oftc/oftc-hybrid/">oftc-hybrid</a>)</li>
    </ul>

and excluding:

    <ul>
        <li><a href="https://github.com/charybdis-ircd/charybdis/">Charybdis</a> and its forks (such as <a href="https://solanum.chat/">Solanum</a>, <a href="https://libera.chat/">Libera.Chat</a>'s server).</li>
        <li><a href="https://ergo.chat/">Ergo</a></li>
        <li><a href="https://www.unrealircd.org/">UnrealIRCd</a></li>
    </ul>
</p>

<p>
    Finally, to exploit this, one needs to make a Matrix puppet join a channel with &CTAB; in its name. My first thought was to exploit one of IRC's "channel forward" features; such as <a href="https://libera.chat/guides/channelmodes">Solanum's <code>+f</code> mode</a> or <a href="https://www.unrealircd.org/docs/Extended_bans#Group_2:_actions">UnrealIRCd's <code>~forward</code> extban</a>; but ircd-hybrid supports neither.
</p>

<p>
    Therefore, I initially concluded this was not exploitable, and <a href="https://github.com/matrix-org/node-irc/pull/87">submitted a simple bug fix</a>; which was accepted.
</p>
            </section>

            <section>
            <h3 id="exploit-mute">Exploit 1: muting Matrix users</h3>

<p>
A month later, I found out that matrix-appservice-irc has a <code>!cmd</code> command; which allows making one's IRC puppet join arbitrary channels. Though Matrix clients generally do not allow typing &CTAB; in a message; it is possible to send a raw Matrix event like this:

<pre>
<![CDATA[
  {
    "body": "!cmd JOIN #chan\t:moo",
    "msgtype": "m.text"
  }
]]>
</pre>

which makes the puppet join &CHANMOO;, so it will see any message sent to that channel
</p>

<p>
Once a Matrix puppet is in &CHANMOO;, operators of &CHANMOO; can send <code>MODE</code> commands to that channel, such as:

<pre>
<![CDATA[
  MODE #chan<TAB>:moo +b :baduser!*@*
]]>
</pre>

which should be parsed as <code>["#chan&TAB;:moo", "+b", "baduser!*@*"]</code>, ie.

<blockquote>
    apply <a href="https://modern.ircdocs.horse/#ban-channel-mode">mode <code>b</code> (ban)</a> to <code>baduser!*@*</code> on &CHANMOO;)
</blockquote>

</p>

<p>
        Instead, it is parsed by node-irc as <code>["#chan", "moo +b :baduser!*@*"]</code>, ie.

<blockquote>
        apply mode characters <code>m</code>, <code>o</code>, <code>o</code>, (space), <code>b</code>, (space), <code>:</code>, etc. to &CHAN;,
</blockquote>
</p>

<p>
Most of these mode characters are ignored by matrix-appservice-irc; but <code>m</code> is interpreted as the <a href="https://modern.ircdocs.horse/#moderated-channel-mode">moderated channel mode</a>, which means unprivileged users are muted.
As a consequence, matrix-appservice-irc alters the power levels of any room bridged to &CHAN;, so users with power level 0 (the default) cannot speak anymore.
</p>

<aside>
(And because I am stupid, I tested this with <code>#oftc</code> instead of &CHAN; so I accidentally muted <code>#_oftc_#oftc:matrix.org</code>, and had to ask the OFTC staff to fix it because I did not figure out how to undo it yet.)
</aside>

<p>
This can also be used to undo modes: by using <code>#chan&TAB;-m</code> instead, matrix-appservice-irc believes mode <code>m</code> is removed, so it restores the normal power level.
</p>
            </section>

            <section>
            <h3 id="exploit-invite">Exploit 2: making rooms public</h3>

<p>
    Possibly a worse example of this, is with <code>#chan&TAB;-i</code>: this makes matrix-appservice-irc believe <a href="https://modern.ircdocs.horse/#invite-only-channel-mode">mode <code>i</code> (invite)</a> is removed, causing it to make any bridged Matrix room joinable by anyone, instead of being <a href="https://spec.matrix.org/v1.3/client-server-api/#mroomjoin_rules">invite-only</a>.
</p>

<p>
    This is somewhat limited by matrix-appservice-irc's design: after someone would join the (now public) Matrix room, matrix-appservice-irc would make its puppet join the IRC channel, which would fail (because the channel is still invite-only), so the Matrix user would be kicked out of the room within seconds (assuming no federation lag). If the user operates their own (malicious) Matrix homeserver and/or causes a DoS to the Matrix homeserver hosting the bridge, this could allow them to retrieve significant part of the history metadata (though not message content, assuming the room is not too old and is properly configured).
</p>
            </section>

            <section>
            <h3 id="exploit-others">Other exploits</h3>

<p>
    Aside from <code>PRIVMSG</code> and <code>MODE</code>, other commands may have been abusable, such as <a href="https://modern.ircdocs.horse/#kick-message"><code>KICK</code></a> (on servers thatallow the absense of kick messages), <a href="https://modern.ircdocs.horse/#part-message"><code>PART</code></a> (ditto, to pretend you left a channel), <a href="https://modern.ircdocs.horse/#topic-message"><code>INVITE</code></a> (to invite only yourself to a room).
</p>

<p>
    Nothing noteworthy as far as I can tell.
</p>
            </section>
            </section>

            <section>
            <h2 id="resolution">Resolution</h2>

<p>
I reported this issue to security@matrix.org as soon as possible; but because I was in a hurry, part of my report was phrased ambiguously and implied <a href="https://github.com/matrix-org/node-irc/pull/87">the fix I submitted</a> was actually the source of the issue, so Element (the company behind Matrix.org, in charge of the security team) <a href="https://github.com/matrix-org/node-irc/pull/89">reverted it</a> and assumed the issue was fixed.
</p>

<p>
After three mails from me asking for news and announcing I would publicly contact bridge operators, Element and I could finally resolve this misunderstanding, and "un-revert" the fix to resolve this issue.
</p>
            </section>

            <section>
            <h2 id="links">Links</h2>
            <ul>
                <li><a href="https://github.com/matrix-org/matrix-appservice-irc/security/advisories/GHSA-xvqg-mv25-rwvw">matrix-appservice-irc advisory</a></li>
                <li><a href="https://matrix.org/blog/2022/09/13/security-release-of-matrix-appservice-irc-0-35-0-high-severity/">Matrix.org's announcement</a></li>
            </ul>
            </section>

            <section>
            <h2 id="timeline">Timeline</h2>

<dl>
    <dt><time>2022-04-26</time></dt>
    <dd>Initial issue found, believed to be benign, and <a href="https://github.com/matrix-org/node-irc/pull/87">patch submitted</a></dd>
    <dt><time>2022-05-25</time></dt>
    <dd>Bug fix accepted, but not tagged/released yet</dd>
    <dt><time>2022-06-06</time></dt>
    <dd>Found out about <code>!cmd</code>, found how to use it to exploit this, and accidentally muted <code>#_oftc_#oftc:matrix.org</code></dd>
    <dt><time>2022-06-06</time></dt>
    <dd>Discussed the issue with OFTC's staff so they can undo it.</dd>
    <dt><time>2022-06-06</time></dt>
    <dd>Reported to security@matrix.org.</dd>
    <dt><time>2022-06-06</time></dt>
    <dd>Got an automatic reply (assigned <code>NVT#1534420</code>).</dd>
    <dt><time>2022-06-09</time></dt>
    <dd><a href="https://github.com/matrix-org/node-irc/pull/89">Patch reverted</a>. I noticed this because I watch the repository; at the time I did not understand the reason</dd>
    <dt><time>2022-08-03</time></dt>
    <dd>I asked security@matrix.org for news. Got no answer.</dd>
    <dt><time>2022-09-01</time></dt>
    <dd>I asked security@matrix.org for news again. Still no answer.</dd>
    <dt><time>2022-09-06</time></dt>
    <dd>The <a href="https://web.archive.org/web/20220820031710/https://matrix.org/security-disclosure-policy/">90 days deadline</a> expired, so I sent another email, summing up my point of view of the issue, and explaining I would publicly speak about this issue a week later if they did not give me reason to believe they would fix the issue within a reasonable time frame.</dd>
    <dt><time>2022-09-07</time></dt>
    <dd>The Security Team apologized for the lack of news, and explained their point of view; so I realized the misunderstanding and we cleared it up. They told me they would fix the issue within the week, along with three others.</dd>
    <dt><time>2022-09-12</time></dt>
    <dd>Patch was <a href="https://github.com/matrix-org/node-irc/pull/93">un-reverted</a></dd>
    <dt><time>2022-09-13</time></dt>
    <dd>Patch was released, deployed on bridges managed by Matrix.Org/EMS, and <a href="https://matrix.org/blog/2022/09/13/security-release-of-matrix-appservice-irc-0-35-0-high-severity/">announced</a></dd>
</dl>
            </section>
        </div>
    </content>
</entry>
