Vulnerability: Question2Answer Q2A Ultimate SEO Version 1.3 is affected by cross-site scripting (XSS), which may lead to arbitrary remote code execution.
Vulnerability ID: CVE-2021-3258
CWE: CWE-79
Severity score: 2.9
Project: https://github.com/q2a-projects/Q2A-Ultimate-SEO
Published: 2021-02-05
File: q2a-ultimate-seo/layer.php
Commit message: Sanitizing Meta output
Commit author: Towhid

Vulnerable version (before fix):
 function doctype(){ 		// Custom Meta(title,description,keywords) 		if( ($this->template=='question') and (qa_opt('useo_meta_editor_enable')) ){ 			$metas = json_decode(qa_db_postmeta_get($this->content['q_view']['raw']['postid'], 'useo-meta-info'),true); 			$this->meta_title = @$metas['title']; 			$this->meta_description = @$metas['description']; 			$this->meta_keywords = @$metas['keywords']; 		} 		// Generate Social Meta Tags 		$page_url = @$this->content['canonical']; 		if(! empty($this->meta_description)) 			$description = $this->meta_description; 		else 			$description = @$this->content['description']; 		if(! empty($this->meta_title)) 			$title = $this->meta_title; 		else 			$title = @$this->content['q_view']['raw']['title']; 		 		if($this->template=='question'){ 			if(qa_opt('useo_social_enable_editor')){ 				$this->social_metas = json_decode(qa_db_postmeta_get($this->content['q_view']['raw']['postid'], 'useo-social-info'),true); 				if(count($this->social_metas)) 					foreach ($this->social_metas as $index => $variable){ 						$this->metas[$index]['content'] = $variable; 						$this->metas[$index]['type'] = ''; 					} 			} function doctype(){ 				// description 				$useo_social_tc_desc_length = qa_opt('useo_social_og_desc_length'); 				if($useo_social_tc_desc_length<=0) 						$useo_social_tc_desc_length = 120; 				$this->metas['tc-description']['content'] = useo_get_excerpt($description, 0, $useo_social_tc_desc_length); 				$this->metas['tc-description']['type'] = 'property="twitter:description"'; 				// image function head_title() 					$title_template = qa_opt('useo_title_qa_item'); 					if(! empty($title_template) ){ 						$search = array( '%site-title%', '%question-title%', '%category-name%'); 						$replace   = array(qa_opt('site_title'), @$this->content['q_view']['raw']['title'], $category_name); 						$title = str_replace($search, $replace, $title_template); 					} 				}else{ function head_title() 		if($this->template=='question'){ 			// setup custom meta keyword 			if (! empty($this->meta_keywords)) 				$this->content['keywords'] = $this->meta_keywords; 			// setup custom meta description 			if (! empty($this->meta_description)) 				$this->content['description'] = $this->meta_description; 			// if there was no custom meta description and it's supposed to read it from answers do it, otherwise don't change it 			elseif(qa_opt('useo_meta_desc_ans_enable')){ 				$lenght = (int)qa_opt('useo_meta_desc_length'); function main_parts($content) 					</tr> 					<tr> 						<td class="qa-form-tall-data"> 							<input placeholder="' . $this->content['q_view']['raw']['title'] . '" id="useo-meta-editor-title" class="qa-form-tall-text" type="text" value="'. $this->meta_title .'" name="useo-meta-editor-title"> 						</td> 					</tr> 				</tbody> function main_parts($content) 					</tr> 					<tr> 						<td class="qa-form-tall-data"> 							<textarea placeholder="' . $this->content['description'] . '" id="useo-meta-editor-description" class="qa-form-tall-text" cols="40" rows="3" name="useo-meta-editor-description">'. $this->meta_description .'</textarea> 						</td> 					</tr> 				</tbody>

Fixed version (after fix):
 function doctype(){ 		// Custom Meta(title,description,keywords) 		if( ($this->template=='question') and (qa_opt('useo_meta_editor_enable')) ){ 			$metas = json_decode(qa_db_postmeta_get($this->content['q_view']['raw']['postid'], 'useo-meta-info'),true); 			$this->meta_title = htmlspecialchars(@$metas['title']); 			$this->meta_description = htmlspecialchars(@$metas['description']); 			$this->meta_keywords = htmlspecialchars(@$metas['keywords']); 		} 		// Generate Social Meta Tags 		$page_url = @$this->content['canonical']; 		if(! empty($this->meta_description)) 			$description = htmlspecialchars($this->meta_description); 		else 			$description = htmlspecialchars(@$this->content['description']); 		if(! empty($this->meta_title)) 			$title = htmlspecialchars($this->meta_title); 		else 			$title = htmlspecialchars(@$this->content['q_view']['raw']['title']); 		 		if($this->template=='question'){ 			if(qa_opt('useo_social_enable_editor')){ 				$this->social_metas = json_decode(qa_db_postmeta_get($this->content['q_view']['raw']['postid'], 'useo-social-info'),true); 				if(count($this->social_metas)) 					foreach ($this->social_metas as $index => $variable){ 						$this->metas[$index]['content'] = htmlspecialchars($variable); 						$this->metas[$index]['type'] = ''; 					} 			} function doctype(){ 				// description 				$useo_social_tc_desc_length = qa_opt('useo_social_og_desc_length'); 				if($useo_social_tc_desc_length<=0) 					$useo_social_tc_desc_length = 120; 				$this->metas['tc-description']['content'] = useo_get_excerpt($description, 0, $useo_social_tc_desc_length); 				$this->metas['tc-description']['type'] = 'property="twitter:description"'; 				// image function head_title() 					$title_template = qa_opt('useo_title_qa_item'); 					if(! empty($title_template) ){ 						$search = array( '%site-title%', '%question-title%', '%category-name%'); 						$replace   = array(qa_opt('site_title'), htmlspecialchars(@$this->content['q_view']['raw']['title']), $category_name); 						$title = str_replace($search, $replace, $title_template); 					} 				}else{ function head_title() 		if($this->template=='question'){ 			// setup custom meta keyword 			if (! empty($this->meta_keywords)) 				$this->content['keywords'] = htmlspecialchars($this->meta_keywords); 			// setup custom meta description 			if (! empty($this->meta_description)) 				$this->content['description'] = htmlspecialchars($this->meta_description); 			// if there was no custom meta description and it's supposed to read it from answers do it, otherwise don't change it 			elseif(qa_opt('useo_meta_desc_ans_enable')){ 				$lenght = (int)qa_opt('useo_meta_desc_length'); function main_parts($content) 					</tr> 					<tr> 						<td class="qa-form-tall-data"> 							<input placeholder="' . htmlspecialchars($this->content['q_view']['raw']['title']) . '" id="useo-meta-editor-title" class="qa-form-tall-text" type="text" value="'. $this->meta_title .'" name="useo-meta-editor-title"> 						</td> 					</tr> 				</tbody> function main_parts($content) 					</tr> 					<tr> 						<td class="qa-form-tall-data"> 							<textarea placeholder="' . htmlspecialchars($this->content['description']) . '" id="useo-meta-editor-description" class="qa-form-tall-text" cols="40" rows="3" name="useo-meta-editor-description">'. $this->meta_description .'</textarea> 						</td> 					</tr> 				</tbody>

Patch:
patch: @@ -12,27 +12,27 @@ function doctype(){
 		// Custom Meta(title,description,keywords)
 		if( ($this->template=='question') and (qa_opt('useo_meta_editor_enable')) ){
 			$metas = json_decode(qa_db_postmeta_get($this->content['q_view']['raw']['postid'], 'useo-meta-info'),true);
-			$this->meta_title = @$metas['title'];
-			$this->meta_description = @$metas['description'];
-			$this->meta_keywords = @$metas['keywords'];
+			$this->meta_title = htmlspecialchars(@$metas['title']);
+			$this->meta_description = htmlspecialchars(@$metas['description']);
+			$this->meta_keywords = htmlspecialchars(@$metas['keywords']);
 		}
 		// Generate Social Meta Tags
 		$page_url = @$this->content['canonical'];
 		if(! empty($this->meta_description))
-			$description = $this->meta_description;
+			$description = htmlspecialchars($this->meta_description);
 		else
-			$description = @$this->content['description'];
+			$description = htmlspecialchars(@$this->content['description']);
 		if(! empty($this->meta_title))
-			$title = $this->meta_title;
+			$title = htmlspecialchars($this->meta_title);
 		else
-			$title = @$this->content['q_view']['raw']['title'];
+			$title = htmlspecialchars(@$this->content['q_view']['raw']['title']);
 		
 		if($this->template=='question'){
 			if(qa_opt('useo_social_enable_editor')){
 				$this->social_metas = json_decode(qa_db_postmeta_get($this->content['q_view']['raw']['postid'], 'useo-social-info'),true);
 				if(count($this->social_metas))
 					foreach ($this->social_metas as $index => $variable){
-						$this->metas[$index]['content'] = $variable;
+						$this->metas[$index]['content'] = htmlspecialchars($variable);
 						$this->metas[$index]['type'] = '';
 					}
 			}
@@ -74,7 +74,7 @@ function doctype(){
 				// description
 				$useo_social_tc_desc_length = qa_opt('useo_social_og_desc_length');
 				if($useo_social_tc_desc_length<=0)
-						$useo_social_tc_desc_length = 120;
+					$useo_social_tc_desc_length = 120;
 				$this->metas['tc-description']['content'] = useo_get_excerpt($description, 0, $useo_social_tc_desc_length);
 				$this->metas['tc-description']['type'] = 'property="twitter:description"';
 				// image
@@ -223,7 +223,7 @@ function head_title()
 					$title_template = qa_opt('useo_title_qa_item');
 					if(! empty($title_template) ){
 						$search = array( '%site-title%', '%question-title%', '%category-name%');
-						$replace   = array(qa_opt('site_title'), @$this->content['q_view']['raw']['title'], $category_name);
+						$replace   = array(qa_opt('site_title'), htmlspecialchars(@$this->content['q_view']['raw']['title']), $category_name);
 						$title = str_replace($search, $replace, $title_template);
 					}
 				}else{
@@ -430,10 +430,10 @@ function head_title()
 		if($this->template=='question'){
 			// setup custom meta keyword
 			if (! empty($this->meta_keywords))
-				$this->content['keywords'] = $this->meta_keywords;
+				$this->content['keywords'] = htmlspecialchars($this->meta_keywords);
 			// setup custom meta description
 			if (! empty($this->meta_description))
-				$this->content['description'] = $this->meta_description;
+				$this->content['description'] = htmlspecialchars($this->meta_description);
 			// if there was no custom meta description and it's supposed to read it from answers do it, otherwise don't change it
 			elseif(qa_opt('useo_meta_desc_ans_enable')){
 				$lenght = (int)qa_opt('useo_meta_desc_length');
@@ -499,7 +499,7 @@ function main_parts($content)
 					</tr>
 					<tr>
 						<td class="qa-form-tall-data">
-							<input placeholder="' . $this->content['q_view']['raw']['title'] . '" id="useo-meta-editor-title" class="qa-form-tall-text" type="text" value="'. $this->meta_title .'" name="useo-meta-editor-title">
+							<input placeholder="' . htmlspecialchars($this->content['q_view']['raw']['title']) . '" id="useo-meta-editor-title" class="qa-form-tall-text" type="text" value="'. $this->meta_title .'" name="useo-meta-editor-title">
 						</td>
 					</tr>
 				</tbody>
@@ -512,7 +512,7 @@ function main_parts($content)
 					</tr>
 					<tr>
 						<td class="qa-form-tall-data">
-							<textarea placeholder="' . $this->content['description'] . '" id="useo-meta-editor-description" class="qa-form-tall-text" cols="40" rows="3" name="useo-meta-editor-description">'. $this->meta_description .'</textarea>
+							<textarea placeholder="' . htmlspecialchars($this->content['description']) . '" id="useo-meta-editor-description" class="qa-form-tall-text" cols="40" rows="3" name="useo-meta-editor-description">'. $this->meta_description .'</textarea>
 						</td>
 					</tr>
 				</tbody>
path: q2a-ultimate-seo/layer.php