CVE-2019-25099: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: A vulnerability classified as critical was found in Arthmoor QSF-Portal. This vulnerability affects unknown code of the file index.php. The manipulation of the argument a leads to path traversal. The patch is identified as ea4f61e23ecb83247d174bc2e2cbab521c751a7d. It is recommended to apply a patch to fix this issue. VDB-217558 is the identifier assigned to this vulnerability.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 5.3
Published: 2023-01-06T13:15Z
Repository: https://github.com/Arthmoor/QSF-Portal
Language: PHP
Files changed: docs/ChangeLog.txt, index.php
Commit date: 2019-04-29 16:38:16 -0600
Commit message: Tweaked the initial module selection code to block out potential attacks and notify administrators when this happens.

Diff: diff --git a/docs/ChangeLog.txt b/docs/ChangeLog.txt
index e311b12..d17ac84 100644
--- a/docs/ChangeLog.txt
+++ b/docs/ChangeLog.txt
@@ -36,10 +36,11 @@ Changes for 2.0:
 * Updated timezone handling to use more modern methods.
 * Updated the upgrade system so that a bunch of little files full of stuff aren't needed anymore. One file is sufficient with all the changes listed in sequence going forward.
 * The forum for news posts is now settable in the ACP instead of being hardcoded in the PHP files.
 * The AdminCP has new icons for decoration.
 * The custom logo section of the CSS has been separated into its own file which should not need to be updated during upgrades.
+* Tweaked the initial module selection code to block out potential attacks and notify administrators when this happens.
 
    Removed:
 
 * Conversion from other ancient forum packages has been removed as they're all woefully obsolete and writing new ones would be a huge time sink for little gain.
 * Removed GTalk/Skype profile fields and replaced them with Twitter ID.
diff --git a/index.php b/index.php
index 1595479..7da7f34 100644
--- a/index.php
+++ b/index.php
@@ -28,14 +28,36 @@
 
 if( version_compare( PHP_VERSION, "7.0.0", "<" ) ) {
 	die( 'PHP version does not meet minimum requirements. Contact your system administrator.' );
 }
 
+function log_hostile_action( $set, $qstring )
+{
+	if( isset( $set['admin_email'] ) ) {
+		$https = isset( $_SERVER['HTTPS'] ) ? 'https://' : 'http://';
+
+		$headers = "From: Your QSF Portal Site <{$set['admin_email']}>\r\n" . "X-Mailer: PHP/" . phpversion();
+
+		$agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : 'N/A';
+		$ip = isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';
+
+		$error_report = "QSF Portal has intercepted a possible attack!\n";
+		$error_report .= "The details are as follows:\n\nURL: $https" . $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF'] . "?" . $qstring . "\n";
+		$error_report .= "Querying user agent: " . $agent . "\n";
+		$error_report .= "Querying IP: " . $ip . "\n\n";
+		$error_report = str_replace( "&nbsp;", " ", html_entity_decode( $error_report ) );
+
+		@mail( $set['admin_email'], "[QSF Portal] Potential Attack Intercepted", $error_report, $headers );
+	}
+}
+
 define( 'QUICKSILVERFORUMS', true );
 
 date_default_timezone_set( 'UTC' );
 
+session_start();
+
 $time_now   = explode( ' ', microtime() );
 $time_start = $time_now[1] + $time_now[0];
 
 srand( (double)microtime() * 1234567 );
 
@@ -90,27 +112,44 @@ if( !isset( $_GET['a'] ) ) {
 	if( isset( $_SERVER['QUERY_STRING'] ) && !empty( $_SERVER['QUERY_STRING'] ) ) {
 		$qstring = $_SERVER['QUERY_STRING'];
 
 		$missing = true;
 	}
-} elseif( !file_exists( 'func/' . $_GET['a'] . '.php' ) ) {
-	$module = 'main';
-
-	if( $_GET['a'] != 'forum_rules' && $_GET['a'] != 'upload_rules' ) {
-		$qstring = $_SERVER['REQUEST_URI'];
+} elseif( !empty( $_GET['a'] ) ) {
+	if( strstr( $_GET['a'], '/' ) || strstr( $_GET['a'], '\\' ) || strstr( $_GET['a'], '.' ) ) {
+		if( isset( $_SERVER['QUERY_STRING'] ) && !empty( $_SERVER['QUERY_STRING'] ) ) {
+			$qstring = $_SERVER['QUERY_STRING'];
+		}
 
 		$missing = true;
+
+		$_SESSION = array();
+
+		session_destroy();
+
+		log_hostile_action( $set, $qstring );
+
+		header( 'Clear-Site-Data: "*"' );
+	} elseif( !file_exists( 'func/' . $_GET['a'] . '.php' ) ) {
+		$module = 'main';
+
+		if( $_GET['a'] != 'forum_rules' && $_GET['a'] != 'upload_rules' ) {
+			$qstring = $_SERVER['REQUEST_URI'];
+
+			$missing = true;
+		} else {
+			$terms_module = $_GET['a'];
+		}
 	} else {
-		$terms_module = $_GET['a'];
+		$module = $_GET['a'];
 	}
 } else {
-	$module = $_GET['a'];
-}
+	if( isset( $_SERVER['QUERY_STRING'] ) && !empty( $_SERVER['QUERY_STRING'] ) ) {
+		$qstring = $_SERVER['QUERY_STRING'];
 
-if( strstr( $module, '/' ) || strstr( $module, '\\' ) ) {
-	header( 'HTTP/1.0 403 Forbidden' );
-	exit( 'You have been banned from this site.' );
+		$missing = true;
+	}
 }
 
 // I know this looks corny and all but it mimics the output from a real 404 page.
 if( $missing ) {
 	header( 'HTTP/1.0 404 Not Found' );
@@ -134,12 +173,10 @@ $qsf->pre = $set['prefix'];
 
 $qsf->get['a'] = $module;
 $qsf->sets     = $qsf->get_settings( $set );
 $qsf->site     = $qsf->sets['loc_of_board']; // Will eventually replace $qsf->self once the SEO URL changes are done.
 
-session_start();
-
 $qsf->user_cl = new user( $qsf );
 $qsf->user    = $qsf->user_cl->login();
 $qsf->lang    = $qsf->get_lang( $qsf->user['user_language'], $qsf->get['a'] );
 
 if( !isset( $qsf->get['skin'] ) ) {

Vulnerable code: } elseif( !file_exists( 'func/' . $_GET['a'] . '.php' ) ) {
	$module = 'main';

	if( $_GET['a'] != 'forum_rules' && $_GET['a'] != 'upload_rules' ) {
		$qstring = $_SERVER['REQUEST_URI'];
		$terms_module = $_GET['a'];
	$module = $_GET['a'];
}
if( strstr( $module, '/' ) || strstr( $module, '\\' ) ) {
	header( 'HTTP/1.0 403 Forbidden' );
	exit( 'You have been banned from this site.' );
session_start();

Fixed code: * Tweaked the initial module selection code to block out potential attacks and notify administrators when this happens.
function log_hostile_action( $set, $qstring )
{
	if( isset( $set['admin_email'] ) ) {
		$https = isset( $_SERVER['HTTPS'] ) ? 'https://' : 'http://';

		$headers = "From: Your QSF Portal Site <{$set['admin_email']}>\r\n" . "X-Mailer: PHP/" . phpversion();

		$agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : 'N/A';
		$ip = isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';

		$error_report = "QSF Portal has intercepted a possible attack!\n";
		$error_report .= "The details are as follows:\n\nURL: $https" . $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF'] . "?" . $qstring . "\n";
		$error_report .= "Querying user agent: " . $agent . "\n";
		$error_report .= "Querying IP: " . $ip . "\n\n";
		$error_report = str_replace( "&nbsp;", " ", html_entity_decode( $error_report ) );

		@mail( $set['admin_email'], "[QSF Portal] Potential Attack Intercepted", $error_report, $headers );
	}
}

session_start();

} elseif( !empty( $_GET['a'] ) ) {
	if( strstr( $_GET['a'], '/' ) || strstr( $_GET['a'], '\\' ) || strstr( $_GET['a'], '.' ) ) {
		if( isset( $_SERVER['QUERY_STRING'] ) && !empty( $_SERVER['QUERY_STRING'] ) ) {
			$qstring = $_SERVER['QUERY_STRING'];
		}

		$_SESSION = array();

		session_destroy();

		log_hostile_action( $set, $qstring );

		header( 'Clear-Site-Data: "*"' );
	} elseif( !file_exists( 'func/' . $_GET['a'] . '.php' ) ) {
		$module = 'main';

		if( $_GET['a'] != 'forum_rules' && $_GET['a'] != 'upload_rules' ) {
			$qstring = $_SERVER['REQUEST_URI'];

			$missing = true;
		} else {
			$terms_module = $_GET['a'];
		}
		$module = $_GET['a'];
	if( isset( $_SERVER['QUERY_STRING'] ) && !empty( $_SERVER['QUERY_STRING'] ) ) {
		$qstring = $_SERVER['QUERY_STRING'];
		$missing = true;
	}