Summary
Downstream projects that vendor Prototype.js 1.7.3 are being flagged by
OWASP dependency-check-maven for CVE-2020-27511 even after they
manually patch out the vulnerable String#stripTags and
String#unescapeHTML functions, because OWASP dependency-check identifies
the library by the Version literal in prototype.js (via the bundled
Retire.js signature database) and
the upstream literal still reads Version: '1.7.3'.
This makes the CI / security-scan story very awkward for any project that
ships a fork of prototype.js: the only path to a green dependency-check
build is to either (a) drop Prototype.js entirely, (b) suppress the finding
project-wide (which masks future Prototype-related CVEs as well), or
(c) rewrite the Version literal at build time to deliberately defeat the
Retire.js extractor. We have taken option (c) in
JavaMelody — see the writeup at
the bottom of this issue — but it would be far better to fix this at the
source.
This issue continues the conversation from #355 (CVE-2020-27511) but is
specifically about the scanner-flag problem for downstream forks, not
the underlying ReDoS itself.
How OWASP dependency-check flags this file
OWASP dependency-check-maven ships with Retire.js's signature database. The
entry for prototypejs
declares a vulnerability range of { "below": "1.7.4" } for CVE-2020-27511
and identifies any prototype.js file via these filecontent extractors:
Prototype JavaScript framework, version (§§version§§)
Prototype[ ]?=[ ]?\{[ \r\n\t]*Version:[ ]?(?:'|")(§§version§§)(?:'|")
where §§version§§ expands to the capture group ([0-9][0-9a-z_\.\-]+).
Since 1.7.3 is the final tagged Prototype.js release and has been since
2015-11-18, every fork in existence still ships these literals and is
therefore flagged as vulnerable — even forks that have removed the
vulnerable methods entirely (so the ReDoS exploit path is unreachable).
Reproducer
- Create a Java project with
dependency-check-maven configured and a
single embedded prototype.js resource taken verbatim from the
master branch of prototypejs/prototype.
- Run
mvn dependency-check:check.
- Observe a HIGH-severity CVE-2020-27511 finding.
- Manually delete
String.prototype.stripTags and
String.prototype.unescapeHTML from the file (both the function
definitions and the entries in the Object.extend(String.prototype, ...)
hash near the end of that section).
- Re-run
mvn dependency-check:check.
- Expected: no finding (the vulnerable code paths are gone).
Actual: the HIGH-severity CVE-2020-27511 finding still appears,
because Retire.js detects the Version: '1.7.3' literal and there is
no entry above 1.7.4 in the vulnerability range.
What would fix this at the source
Any one of the following would unblock downstream forks:
- Tag a 1.7.4 release (no functional changes required) that simply
bumps the literal Version: '1.7.3' and the header comment to
1.7.4. The Retire.js comparator would then place patched files
outside the below: 1.7.4 range. This is the lowest-friction option.
- Apply the CVE-2020-27511 patch on the
master branch (e.g. remove
or rewrite stripTags / unescapeHTML) and bump the version
literal in the same commit, then tag 1.7.4. This also fixes the
underlying ReDoS for anyone re-vendoring from master.
- Officially mark the project as archived / unmaintained with a
pointer in the README to the recommended migration path. SCA vendors
typically respect explicit upstream EOL status when triaging.
Of these, (1) is the most pragmatic given the project's release cadence.
What we did in our fork (JavaMelody)
For projects that need a green build today, here is the workaround we
landed in JavaMelody:
- The vulnerable
stripTags and unescapeHTML methods were removed in
Dec 2023 (commit f5720f99). Neither method is called anywhere in
JavaMelody, so the CVE's exploit path is not reachable.
- We rewrite the
Version literal at Maven resource-filter time to
'jm-${project.version}-fork-of-prototypejs-1.7.3'. The leading j
fails Retire.js's [0-9]-anchored version-extractor regex, so the file
is no longer identified as a Prototype.js release of any version, while
the trailing fork-of-prototypejs-1.7.3 substring keeps the upstream
lineage discoverable for human auditors.
- We rewrite the file header so it no longer contains the literal
substring , version (the first Retire.js filecontent extractor).
- We added a JUnit regression test that fails the build if the Version
literal ever reverts to 1.7.3, and a prototype.js.CHANGES.md
documenting the full local diff from upstream.
This works but it is fragile (it relies on the exact shape of Retire.js's
regexes never tightening) and it is not a solution we can recommend to
other downstream projects without effectively asking them to lie to their
SCA tooling. A real fix needs to land upstream.
Happy to send a PR doing option (1) or (2) if that would help.
Summary
Downstream projects that vendor Prototype.js 1.7.3 are being flagged by
OWASP
dependency-check-mavenfor CVE-2020-27511 even after theymanually patch out the vulnerable
String#stripTagsandString#unescapeHTMLfunctions, because OWASP dependency-check identifiesthe library by the
Versionliteral inprototype.js(via the bundledRetire.js signature database) and
the upstream literal still reads
Version: '1.7.3'.This makes the CI / security-scan story very awkward for any project that
ships a fork of
prototype.js: the only path to a green dependency-checkbuild is to either (a) drop Prototype.js entirely, (b) suppress the finding
project-wide (which masks future Prototype-related CVEs as well), or
(c) rewrite the
Versionliteral at build time to deliberately defeat theRetire.js extractor. We have taken option (c) in
JavaMelody — see the writeup at
the bottom of this issue — but it would be far better to fix this at the
source.
This issue continues the conversation from #355 (CVE-2020-27511) but is
specifically about the scanner-flag problem for downstream forks, not
the underlying ReDoS itself.
How OWASP dependency-check flags this file
OWASP
dependency-check-mavenships with Retire.js's signature database. Theentry for
prototypejsdeclares a vulnerability range of
{ "below": "1.7.4" }for CVE-2020-27511and identifies any
prototype.jsfile via thesefilecontentextractors:where
§§version§§expands to the capture group([0-9][0-9a-z_\.\-]+).Since
1.7.3is the final tagged Prototype.js release and has been since2015-11-18, every fork in existence still ships these literals and is
therefore flagged as vulnerable — even forks that have removed the
vulnerable methods entirely (so the ReDoS exploit path is unreachable).
Reproducer
dependency-check-mavenconfigured and asingle embedded
prototype.jsresource taken verbatim from themasterbranch ofprototypejs/prototype.mvn dependency-check:check.String.prototype.stripTagsandString.prototype.unescapeHTMLfrom the file (both the functiondefinitions and the entries in the
Object.extend(String.prototype, ...)hash near the end of that section).
mvn dependency-check:check.Actual: the HIGH-severity CVE-2020-27511 finding still appears,
because Retire.js detects the
Version: '1.7.3'literal and there isno entry above 1.7.4 in the vulnerability range.
What would fix this at the source
Any one of the following would unblock downstream forks:
bumps the literal
Version: '1.7.3'and the header comment to1.7.4. The Retire.js comparator would then place patched filesoutside the
below: 1.7.4range. This is the lowest-friction option.masterbranch (e.g. removeor rewrite
stripTags/unescapeHTML) and bump the versionliteral in the same commit, then tag 1.7.4. This also fixes the
underlying ReDoS for anyone re-vendoring from
master.pointer in the README to the recommended migration path. SCA vendors
typically respect explicit upstream EOL status when triaging.
Of these, (1) is the most pragmatic given the project's release cadence.
What we did in our fork (JavaMelody)
For projects that need a green build today, here is the workaround we
landed in JavaMelody:
stripTagsandunescapeHTMLmethods were removed inDec 2023 (commit
f5720f99). Neither method is called anywhere inJavaMelody, so the CVE's exploit path is not reachable.
Versionliteral at Maven resource-filter time to'jm-${project.version}-fork-of-prototypejs-1.7.3'. The leadingjfails Retire.js's
[0-9]-anchored version-extractor regex, so the fileis no longer identified as a Prototype.js release of any version, while
the trailing
fork-of-prototypejs-1.7.3substring keeps the upstreamlineage discoverable for human auditors.
substring
, version(the first Retire.jsfilecontentextractor).literal ever reverts to
1.7.3, and aprototype.js.CHANGES.mddocumenting the full local diff from upstream.
This works but it is fragile (it relies on the exact shape of Retire.js's
regexes never tightening) and it is not a solution we can recommend to
other downstream projects without effectively asking them to lie to their
SCA tooling. A real fix needs to land upstream.
Happy to send a PR doing option (1) or (2) if that would help.