CVE-2018-25065: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: A vulnerability was found in Wikimedia mediawiki-extensions-I18nTags and classified as problematic. This issue affects some unknown processing of the file I18nTags_body.php of the component Unlike Parser. The manipulation leads to cross site scripting. The attack may be initiated remotely. The identifier of the patch is b4bc3cbbb099eab50cf2b544cf577116f1867b94. It is recommended to apply a patch to fix this issue. The identifier VDB-217445 was assigned to this vulnerability.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2023-01-05T10:15Z
Repository: https://github.com/wikimedia/mediawiki-extensions-I18nTags
Language: JSON
Files changed: I18nTags_body.php, extension.json
Commit date: 2018-08-06 14:03:40 +0200
Commit message: SECURITY: Parse tag function input as wikitext to prevent XSS

Unlike parser functions, tag functions' output is unescaped by
default.

Bug: T200973
Change-Id: I63ea5b7b1edd96a4b9fe8837eb7979faa80b5f78

Diff: diff --git a/I18nTags_body.php b/I18nTags_body.php
index 7022fcd..39131c9 100644
--- a/I18nTags_body.php
+++ b/I18nTags_body.php
@@ -7,24 +7,26 @@ class I18nTags {
 		$parser->setHook( 'plural', [ __CLASS__, 'plural' ] );
 		$parser->setHook( 'linktrail', [ __CLASS__, 'linktrail' ] );
 		$parser->setFunctionHook( 'languagename',  [ __CLASS__, 'languageName' ] );
 	}
 
-	public static function formatNumber( $data, $params, $parser ) {
+	public static function formatNumber( $data, $params, $parser, $frame ) {
 		$lang = self::languageObject( $params );
 
-		return $lang->formatNum( $data );
+		$text = $lang->formatNum( $data );
+		return $parser->recursiveTagParse( $text, $frame );
 	}
 
-	public static function grammar( $data, $params, $parser ) {
+	public static function grammar( $data, $params, $parser, $frame ) {
 		$case = isset( $params['case'] ) ? $params['case'] : '';
 		$lang = self::languageObject( $params );
 
-		return $lang->convertGrammar( $data, $case );
+		$text = $lang->convertGrammar( $data, $case );
+		return $parser->recursiveTagParse( $text, $frame );
 	}
 
-	public static function plural( $data, $params, $parser ) {
+	public static function plural( $data, $params, $parser, $frame ) {
 		list( $from, $to ) = self::getRange( isset( $params['n'] ) ? $params['n'] : '' );
 		$args = explode( '|', $data );
 		$lang = self::languageObject( $params );
 
 		$format = isset( $params['format'] ) ? $params['format'] : '%s';
@@ -39,14 +41,14 @@ class I18nTags {
 				[ $i, wfMsgReplaceArgs( $t, [ $fmtn ] ) ],
 				$format
 			);
 		}
 
-		return $s;
+		return $parser->recursiveTagParse( $s, $frame );
 	}
 
-	public static function linktrail( $data, $params, $parser ) {
+	public static function linktrail( $data, $params, $parser, $frame ) {
 		$lang = self::languageObject( $params );
 		$regex = $lang->linkTrail();
 
 		$inside = '';
 		if ( '' != $data ) {
@@ -58,11 +60,12 @@ class I18nTags {
 				$data = $m[2];
 			}
 		}
 		$predata = isset( $predata[2] ) ? $predata[2] : isset( $predata[1] ) ? $predata[1] : $predata[0];
 
-		return "<strong>$predata$inside</strong>$data";
+		$text = "<strong>$predata$inside</strong>$data";
+		return $parser->recursiveTagParse( $text, $frame );
 	}
 
 	public static function languageName( &$parser, $code = '', $outputLanguage = '' ) {
 		if ( !$code ) {
 			return '';
diff --git a/extension.json b/extension.json
index 391c692..bbdd9e4 100644
--- a/extension.json
+++ b/extension.json
@@ -1,8 +1,8 @@
 {
 	"name": "Parser i18n tags",
-	"version": "2016-02-20",
+	"version": "2018-08-06",
 	"author": "Niklas Laxström",
 	"url": "https://www.mediawiki.org/wiki/Extension:I18nTags",
 	"descriptionmsg": "i18ntags-desc",
 	"type": "parserhook",
 	"license-name": "GPL-2.0-or-later",

Vulnerable code: 	public static function formatNumber( $data, $params, $parser ) {
		return $lang->formatNum( $data );
	public static function grammar( $data, $params, $parser ) {
		return $lang->convertGrammar( $data, $case );
	public static function plural( $data, $params, $parser ) {
		return $s;
	public static function linktrail( $data, $params, $parser ) {
		return "<strong>$predata$inside</strong>$data";
	"version": "2016-02-20",
Fixed code: 	public static function formatNumber( $data, $params, $parser, $frame ) {
		$text = $lang->formatNum( $data );
		return $parser->recursiveTagParse( $text, $frame );
	public static function grammar( $data, $params, $parser, $frame ) {
		$text = $lang->convertGrammar( $data, $case );
		return $parser->recursiveTagParse( $text, $frame );
	public static function plural( $data, $params, $parser, $frame ) {
		return $parser->recursiveTagParse( $s, $frame );
	public static function linktrail( $data, $params, $parser, $frame ) {
		$text = "<strong>$predata$inside</strong>$data";
		return $parser->recursiveTagParse( $text, $frame );
	"version": "2018-08-06",