<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/_stylesheets/atom_stylesheet.xsl"?>
<entry
        xmlns="http://www.w3.org/2005/Atom"
        xmlns:pktz="https://pktz.fr/schema/"
>
    <title>Incorrect IRC mode matching matrix-appservice-irc</title>
    <id>https://pktz.fr/matrix/security/2022-appservice-irc-mode-matching/</id>
    <published>2022-09-23</published>
    <updated>2022-09-23</updated>
    <author>
        <name>Val Lorentz</name>
        <uri>https://valentin-lorentz.fr/</uri>
    </author>
    <pktz:keywords>
        Matrix, matrix-appservice-irc, NVT#1533594, NVT#1536508, NVT#1537667, NVT#1537840, GHSA-cq7q-5c67-w39w, CVE-2022-39202
    </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="background">Background</h3>

<p>
The <a href="https://modern.ircdocs.horse/#mode-message">MODE command</a> is used in the IRC protocol to change channel properties and permissions.
</p>

<p>
It has the following syntax:

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

<p>
and the <code>&lt;modestring&gt;</code> is made of letters. For example, this:

<pre>
<![CDATA[
  MODE +bo baduser!*@* gooduser
]]>
</pre>

is equivalent to:

<pre>
<![CDATA[
  MODE +b baduser!*@*
  MODE +o gooduser
]]>
</pre>

which bans (<code>+b</code>) "baduser" and adds "gooduser" to channel operators (<code>+o</code>).
</p>


<p>
However, not all letters take an argument. For example, this:

<pre>
<![CDATA[
  MODE +mo gooduser
]]>
</pre>

is equivalent to:

<pre>
<![CDATA[
  MODE +m
  MODE +o gooduser
]]>
</pre>

which sets the channel to moderated mode (<code>+m</code>), and adds "gooduser" to channel operators (<code>+o</code>).
</p>

<p>
This is described in details here <a href="https://modern.ircdocs.horse/#mode-message">in the specification</a>
</p>

            </section>

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

<p>
Clients should rely on <a href="https://modern.ircdocs.horse/#chanmodes-parameter">ISUPPORT CHANMODES</a> to know which letters expect an argument.
</p>

<p>
However, even though node-irc parses <a href="https://github.com/matrix-org/node-irc/blob/1.2.1/src/irc.ts#L396">ISUPPORT CHANMODES</a> and <a href="https://github.com/matrix-org/node-irc/blob/1.2.1/src/irc.ts#L432">ISUPPORT PREFIX</a>, it did not actually use them to parse MODE commands.
</p>

<p>
Instead, it <a href="https://github.com/matrix-org/node-irc/blob/1.2.1/src/irc.ts#L536-L571">relied on the following algorithm</a>:

<ol>
    <li>if this is a mode declared in ISUPPORT PREFIX, then expect an argument</li>
    <li>if it is mode "b", "k", or "l", then expect an argument</li>
    <li>otherwise, expect no argument</li>
</ol>

which is very inconsistent with what IRC servers emit.
</p>


<p>
This can be tricky to exploit, so here are several examples below, in order of increasing complexity.
</p>

            </section>

            <section>
            <h3 id="example-qo-mute">Example 1: Muting and opering</h3>

<p>
When a channel operator sends the following command:

<pre>
<![CDATA[
  MODE +qo baduser gooduser
]]>
</pre>

    this means, on <a href="https://solanum.chat/">Solanum</a> (the server used by <a href="https://libera.chat/">Libera.Chat</a>), that the server should <a href="https://libera.chat/guides/channelmodes">quiet (<code>+q</code>)</a> "baduser" and make "gooduser" an <a href="https://modern.ircdocs.horse/#operator-prefix">operator (<code>+o</code>)</a>.
</p>

<p>
    However, because of step 3 of the algorithm above, node-irc / matrix-appservice-irc assumes <code>q</code> takes no argument, so it parses it as equivalent to:

<pre>
<![CDATA[
  MODE +q
  MODE +o baduser
]]>
</pre>

which would make "baduser" an operator
</p>

<p>
    However, this is severely limited by an implementation detail of Solanum: when used as argument of <code>+q</code>, "baduser" is actually rewritten to "baduser!*@*" before being sent to matrix-appservice-irc.
</p>

<p>
I was not able to find a way to exploit this, as all common IRC servers either append "!*@*" arguments or validate them, so they cannot be nicknames.
It is an implementation detail and not guaranteed by the protocol, though.
</p>

<p>
A big caveat: this is somewhat pointless, as it would very unusual for channel operators to send <code>+qo</code> as a single command; and malicious operators could mess with the channel is other ways.
</p>


            </section>

            <section>
            <h3 id="example-lo">Example 2: de-opping not being applied</h3>

<p>
When a channel operator sends the following command:

<pre>
<![CDATA[
  MODE -lo disgraceduser
]]>
</pre>

this means that the server should unset the <a href="https://modern.ircdocs.horse/#client-limit-channel-mode">channel user limit (<code>-l</code>)</a> and remove "disgraceduser" from the operators.
</p>

<p>
However, because of step 2 of the algorithm above, node-irc/matrix-appservice-irc assumes "l" takes an argument (which is true of <code>+l</code>, but not of <code>-l</code>), so it parses it as equivalent to:

<pre>
<![CDATA[
  MODE -l disgraceduser
  MODE -o
]]>
</pre>

causing the last line to be ignored, and "disgraceduser" is not removed
from the operators.
</p>

<p>
The consequence is that, if "disgraceduser" is connected from Matrix, they will keep their power over the Matrix room.
    Either way, they will also appear to other clients as room moderator, but this is without any meaningful consequences, as far as I can tell. (Though it could be abusable if any Matrix bot gives power based on apparent power level, like <a href="https://bitbot.dev/">BitBot</a> does on IRC? I am not aware of any example of this.)
</p>

<p>
Same caveat as before.
</p>


            </section>

            <section>
            <h3 id="example-lov">Example 3: opping the wrong person</h3>

<p>
The following command:

<pre>
<![CDATA[
  MODE -l-o+v gooduser niceuser
]]>
</pre>

is equivalent to:

<pre>
<![CDATA[
  MODE -l
  MODE -o gooduser
  MODE +v niceuser
]]>
</pre>
</p>

<p>
but is interpreted by node-irc/matrix-appservice-irc as:

<pre>
<![CDATA[
  MODE -l gooduser
  MODE -o niceuser
  MODE +v
]]>
</pre>

making "niceuser" an operator instead of "gooduser"
</p>

<p>
This is of little consequence, as far as I can tell; for the same reason as before
</p>

            </section>

            <section>
            <h3 id="example-lov-protected">Example 4: Evading the "protected" channel mode</h3>

<p>
    If "gooduser" has both channel modes <a href="https://modern.ircdocs.horse/#protected-prefix"><code>+a</code> ("protected")</a> and +o (operator), normal operators cannot remove their operator status.
</p>

<p>
(This may be true as well for servers with implement <a href="https://modern.ircdocs.horse/#founder-prefix"><code>+q</code> as "channel owner"</a>, but I did not test it)
</p>

<p>
However, operators can remove it from their Matrix users if mode <code>+a</code> is not <a href="https://modern.ircdocs.horse/#founder-prefix">configured in the <code>modePowerMap</code></a>:

</p>

<p>


The following command:

<pre>
<![CDATA[
  MODE -l-o+v random-op gooduser
]]>
</pre>

is equivalent to:

<pre>
<![CDATA[
  MODE -l
  MODE -o random-op
  MODE +v gooduser
]]>
</pre>

(which is legal for operators, even if "gooduser" is protected)
</p>

<p>

but is interpreted by node-irc/matrix-appservice-irc as:

<pre>
<![CDATA[
  MODE -l random-op
  MODE -o gooduser
  MODE +v
]]>
</pre>

(which isn't)
</p>

<p>
This allows malicious channel operators to remove privileges from "protected" Matrix users, and make it look like to Matrix users they removed privileges from other (IRC or Matrix) users.
</p>


            </section>
            </section>

            <section>
            <h2 id="conclusion">Conclusion</h2>

<p>
This means that, under very specific circumstances, matrix-appservice-irc could interpret some users as being made operators, or miss that they are removed from operators. When such users are Matrix users, it may give them power they should not have over Matrix rooms.
</p>

<p>
This is, however, very limited, because (besides in the example of the uncommon "protected" mode), the user sending commands must have power superior or equal to the attacker; so this can only be triggered accidentally, or by tricking IRC channel operators into typing and running these commands.
</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>
            </section>

            <section>
            <h2 id="links">Links</h2>
            <ul>
                <li><a href="https://github.com/matrix-org/matrix-appservice-irc/security/advisories/GHSA-cq7q-5c67-w39w">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>
                <li><a href="https://github.com/matrix-org/node-irc/commit/c05a4c08e506acc64a1927c8ca1a9cac13af8b60">matrix-org/node-irc patch</a></li>
            </ul>
            </section>

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

<dl>
    <dt><time>2022-05-12</time></dt>
    <dd>Reported to security@matrix.org (the content above as-is, minus some reformatting and links to the IRC specification).</dd>
    <dt><time>2022-05-13</time></dt>
    <dd>Got an automatic reply with links to a private repo, followed by a message explaining security@matrix.org's ticketing system is having issues. (assigned <code>NVT#1532845</code>).</dd>
    <dt><time>2022-08-03</time></dt>
    <dd>I asked security@matrix.org for news. Got no answer. (erroneously assigned <code>NVT#1536508</code> by the ticketing system)</dd>
    <dt><time>2022-09-01</time></dt>
    <dd>I asked security@matrix.org for news again. Still no answer. (erroneously assigned <code>NVT#1537667</code> by the ticketing system)</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 weeks ago, 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. (erroneously assigned <code>NVT#1537840</code> by the ticketing system)</dd>
    <dt><time>2022-09-07</time></dt>
    <dd>As a reply to a <a href="../2022-appservice-irc-tokenization/">different ticket</a>, the Security Team apologized for the lack of news, and reprioritized this ticket. They told me they would fix the issue within the week, along with three others.</dd>
    <dt><time>2022-09-13 08:23</time></dt>
    <dd><a href="https://github.com/matrix-org/node-irc/commit/c05a4c08e506acc64a1927c8ca1a9cac13af8b60">The bug was patched</a>, then security team announced the bug was fixed and <a href="https://github.com/matrix-org/matrix-appservice-irc/security/advisories/GHSA-cq7q-5c67-w39w">released details</a></dd>
    <dt><time>2022-09-13 17:03</time></dt>
    <dd>I missed the patch, so I asked the security team for details about what they did about this bug</dd>
    <dt><time>2022-09-14 18:15</time></dt>
    <dd>
        Given the lack of reply within 24 hours, I pinged a friend who works at Element
    </dd>
    <dt><time>2022-09-23</time></dt>
    <dd>Still no reply. However, while re-reading recent changes, I finally noticed the patch. Published this post.</dd>
</dl>
            </section>
        </div>
    </content>
</entry>
