Closed Bug 1948833 (CVE-2025-2792) Opened 8 months ago Closed 7 months ago

ReDoS Vulnerability in Readability.js Due to Catastrophic Backtracking

Categories

(Toolkit :: Reader Mode, defect)

defect

Tracking

()

RESOLVED FIXED
138 Branch
Tracking Status
firefox-esr115 --- wontfix
firefox-esr128 --- wontfix
firefox136 --- wontfix
firefox137 --- wontfix
firefox138 --- fixed

People

(Reporter: 2922897389, Assigned: Gijs)

References

Details

(Keywords: csectype-dos, reporter-external, sec-other, Whiteboard: [client-bounty-form][fixed by bug 1951537][adv-main138-])

Summary

The regular expression /(.*)[\|\-\\\/>»] .*/gi used in the curTitle = origTitle.replace(/(.*)[\|\-\\\/>»] .*/gi, "$1"); statement is vulnerable to Regular Expression Denial of Service (ReDoS). When processing a maliciously crafted input string, such as "" + "1".repeat(100000) + "A\nA - A", the regex engine enters catastrophic backtracking due to the inefficient pattern design. This causes the application to hang or consume excessive CPU resources, leading to a denial of service condition. Attackers can exploit this vulnerability to degrade system performance or crash the application, impacting availability and potentially disrupting services.

Details

The vulnerability lies in the regular expression /(.*)[\|\-\\\/>»] .*/gi used in the following code:
https://github.com/laurent22/joplin/blob/fb181cf935fa130ef86aa5d1490aaa63c38b9bfd/packages/app-clipper/content_scripts/Readability.js#L486
The regular expression is designed to match and remove a suffix from a string, starting from a specific set of delimiter characters (|, -, \, /, >, ») followed by a space and any subsequent characters. However, the pattern is inefficient and prone to catastrophic backtracking.

PoC

Here is my gist URL: https://gist.github.com/ShiyuBanzhou/c0364cac6e714b25804314f5c3385615

const { JSDOM } = require('jsdom');
const Readability = require('./packages/app-clipper/content_scripts/Readability.js');

const dom = new JSDOM(`<!DOCTYPE html><html><head><title></title></head><body></body></html>`);
const document = dom.window.document;

const attackString = ""+"1".repeat(100000)+"A\nA - A";

const options = {
    documentElement: true,
    title: ""+"1".repeat(100000)+"A\nA - A",
    firstChild:"<!DOCTYPE html><html><head><title></title></head><body></body></html>",
    getElementsByTagName: function() {
        return [document.createElement("div")];
    }
};

document.title = attackString;

const readability = new Readability(document, options);

readability.parse();
  1. run npm install jsdom
  2. place poc.js in the joplin root directory
  3. run node poc.js
    result:
  4. High CPU usage and response logic lag

Impact

This is a Regular Expression Denial of Service (ReDoS) vulnerability. ReDoS occurs when a poorly designed regular expression is evaluated against a maliciously crafted input, causing the regex engine to enter a state of catastrophic backtracking. This results in excessive CPU and memory usage, leading to application hangs, crashes, or severe performance degradation.

Summary

This PR addresses a Regular Expression Denial of Service (ReDoS) vulnerability in the curTitle assignment logic. The original regular expression /(.*)[\|\-\\\/>»] .*/gi was prone to catastrophic backtracking when processing certain inputs, leading to potential denial of service (DoS) attacks. This fix modifies the regex to prevent excessive backtracking and improve performance.

Changes

  • Original Code:
curTitle = origTitle.replace(/(.*)[\|\-\\\/>»] .*/gi, "$1");
  • Updated Code:
curTitle = origTitle.replace(/(?<!.)(.*)[\|\-\\\/>»] .*/gi, "$1");

Related Issues

Fixes Security Advisory.

Flags: sec-bounty?
Group: firefox-core-security → core-security
Component: Security → JavaScript Engine
Product: Firefox → Core
Group: core-security → javascript-core-security

Moving this bug because it's about a specific regular expression in Readability.js.

Group: javascript-core-security → firefox-core-security
Component: JavaScript Engine → Reader Mode
Product: Core → Toolkit

This doesn't actually reproduce in Firefox for me. Do you have a testcase that produces a problem in Firefox?

Even something like this in the console:

"1".repeat(10000000)+"A\nA - A".replace(/(.*)[\|\-\\\/>»] .*/gi, "$1")

(note: several more 0s than the examples in comment 0)

runs in a few ms in the browser console. No hang, no DoS, no nothing. From reading up about catastrophic backtracking, I also do not understand how this particular regular expression would be vulnerable to this, as there are no two adjacent subgroups that can both be expanded/contracted to make multiple matches.

(In reply to DayShift from comment #0)

Changes

  • Original Code:
curTitle = origTitle.replace(/(.*)[\|\-\\\/>»] .*/gi, "$1");
  • Updated Code:
curTitle = origTitle.replace(/(?<!.)(.*)[\|\-\\\/>»] .*/gi, "$1");

You've not explained why this makes any difference or is better. I can believe that the replace call is inefficient and should be replaced but I'd probably do it differently... As far as I can tell the ?<! thing effectively just anchors the match at the start - so rather than expressing "not preceded by .", could just specify ^ (ie has to match at start), which would be a lot easier to read. But anyway the same logic expressed here could be done by removing the entire start of the expression and replacing /[\|\-\\\/>»] .*$/gi with empty string, or finding the index of /[\|\-\\\/>»] /g and substringing.

Still, it's unclear why you claim this regular expression is so inefficient because I can't reproduce the actual problem.

Related Issues

Fixes Security Advisory.

This link is a 404 for me. And more generally that repo is not associated with Mozilla so it's not clear to me if/how this is related.

Flags: needinfo?(2922897389)

This was filed using the bug bounty form and its not clear if you are seeking bug bounty or if you just stumbled on that link as a way to report a security bug. Either way, https://github.com/laurent22/joplin/ isn't our code and for all we know is using an old copy of our Readbility package. Yes, it appears our copy still has that regexp, but It would have been helpful to file it against our code and not make us search for how this is relevant to us.

The security advisory link returns a 404, but the gist is public. This doesn't need to be hidden if the problem is public. Is there a typo in security advisory link, or is it a future one that is currently still private?

I guess this is a bug report againt https://github.com/mozilla/readability, which has a security policy that says to file security bugs here. It does not appear to be a problem in Firefox.

I tried the following in Firefox, Chrome, and Safari in their respective web consoles:

  attackString = ""+"1".repeat(100000)+"A\nA - A";
  attackString.replace(/(.*)[\|\-\\\/>»] .*/gi, "$1") ;
  document.title = attackString;
  document.title.substr(-7);

Chrome handled the .replace() OK, Safari bogged down a bit, and Firefox threw "Uncaught InternalError: too much recursion". All seem reasonable responses to excessive input.

All three engines stripped the newline out of the title and converted it to a space, and none of the engines had a problem running the .replace() regexp on the document's title. Because of this, this does not appear to be a DOS in Firefox itself. You can test this by setting document.title to that attackString, or an even longer one, and then clicking the reader mode button.

The POC in the gist tells Readability to use a replacement getElementsByTagName() function and title. That makes this look more like an example of a problem in the code calling the library than a problem in the Readability library itself.

Summary: ReDoS Vulnerability Due to Catastrophic Backtracking → ReDoS Vulnerability in Readability.js Due to Catastrophic Backtracking

weirdly, the form you tested in comment 2 is fine but explicitly assigning to a string makes it worse. Compare

   "1".repeat(10000000)+"A\nA - A".replace(/(.*)[\|\-\\\/>»] .*/gi, "$1");

with

x = "1".repeat(10000000)+"A\nA - A"; x.replace(/(.*)[\|\-\\\/>»] .*/gi, "$1");

Hello, first of all, thank you for your reply. Actually, I am not here for the bounty. I did find the corresponding code problem in the joplin repository on GitHub, and submitted a private GHSA to try to help the repository manager solve the corresponding problem and report the corresponding vulnerability.
Hello, first of all, thank you for your reply. Actually, I didn't come here for the bounty. I did find the corresponding code problem in the joplin repository on github, and submitted a private GHSA to try to help the repository manager solve the corresponding problem and report the corresponding vulnerability.
And received a reply from the developer: Hello, as mentioned in the email this should be reported to Readability instead: https://github.com/mozilla/readability
So I went to the readability library and found this platform for submitting vulnerabilities in the corresponding column of its security advisory, and submitted the vulnerability as mentioned in comment 0 above. I did implement the attack in the above poc in the readability library and found the ReDos problem, but the type annotation I reported may not be accurate.
I am indeed the discoverer and reporter of the vulnerability, but I may not have ensured the accuracy of the type correctly. Please forgive me.

Flags: needinfo?(2922897389)

(In reply to DayShift from comment #7)

Hello, first of all, thank you for your reply. Actually, I am not here for the bounty. I did find the corresponding code problem in the joplin repository on GitHub, and submitted a private GHSA to try to help the repository manager solve the corresponding problem and report the corresponding vulnerability.
Hello, first of all, thank you for your reply. Actually, I didn't come here for the bounty. I did find the corresponding code problem in the joplin repository on github, and submitted a private GHSA to try to help the repository manager solve the corresponding problem and report the corresponding vulnerability.
And received a reply from the developer: Hello, as mentioned in the email this should be reported to Readability instead: https://github.com/mozilla/readability
So I went to the readability library and found this platform for submitting vulnerabilities in the corresponding column of its security advisory, and submitted the vulnerability as mentioned in comment 0 above. I did implement the attack in the above poc in the readability library and found the ReDos problem, but the type annotation I reported may not be accurate.
I am indeed the discoverer and reporter of the vulnerability, but I may not have ensured the accuracy of the type correctly. Please forgive me.

Flags: needinfo?(gijskruitbosch+bugs)

I will try to get back to this but at the moment the severity is not such that it is likely to be near the top of my list, so it may take some time.

Flags: needinfo?(gijskruitbosch+bugs)
See Also: → 1951537

I released readability 0.6.0 - it would be useful if you could confirm if you think the issue is fixed with that or not.

Flags: needinfo?(2922897389)

(In reply to :Gijs (he/him) from comment #10)

I released readability 0.6.0 - it would be useful if you could confirm if you think the issue is fixed with that or not.

Hello, thank you for your patient reply and hard work. I have checked the modification suggestions according to your instructions. I think your modification is very effective and can solve the problem. Thank you!

Flags: needinfo?(2922897389)

If you have any new comments, please feel free to contact me!

Flags: needinfo?(gijskruitbosch+bugs)

Hello, sorry to bother you. I would like to ask you what we should do next. Should we apply for a CVE number? Do you need my help?

Flags: needinfo?(gijskruitbosch+bugs)

If this is fixed we can close this out.

For the CVE, I don't know -> passing that to Tom.

Assignee: nobody → gijskruitbosch+bugs
Status: UNCONFIRMED → RESOLVED
Closed: 7 months ago
Flags: needinfo?(tom)
OS: Unspecified → All
Hardware: Unspecified → All
Resolution: --- → FIXED
Whiteboard: [client-bounty-form] → [client-bounty-form][fixed by bug 1951537]
Target Milestone: --- → 138 Branch

Thank you for your positive recovery and patience, thank you!

Group: firefox-core-security → core-security-release

Because this is (at most) a content process Denial of Service is does not meet the bounty criteria; however because it is potentially an issue in a library others may use we will issue a CVE for it through Github. I will do that, and leave my ni? open to remind myself.

Flags: sec-bounty? → sec-bounty-

Ok, let's do this. I don't need a bounty, we just need to publish the CVE to alert other developers, thank you.

Hello, may I ask if everything is going well for you? Is there anything I can do for you?

(In reply to DayShift from comment #18)

Hello, may I ask if everything is going well for you? Is there anything I can do for you?

I'm sorry I haven't gotten to it yet, but I will try to this week. For status updates about CVEs/bounties/etc you can email security@mozilla.org rather than using Bugzilla.

Alias: CVE-2025-2792
Group: core-security-release
Flags: needinfo?(tom)
Whiteboard: [client-bounty-form][fixed by bug 1951537] → [client-bounty-form][fixed by bug 1951537][adv-main138+]
Flags: sec-bounty-hof+
Whiteboard: [client-bounty-form][fixed by bug 1951537][adv-main138+] → [client-bounty-form][fixed by bug 1951537][adv-main138-]
You need to log in before you can comment on or make changes to this bug.