Today, we are releasing XenForo 2.2.17 to address a potential security vulnerability. We recommend that all customers running XenForo 2.2 upgrade to 2.2.17 or use the patch instructions below as soon as possible.
Notes:
a. XenForo 2.3.1 and above is not affected by this issue. If you are still running XenForo 2.3.0 you should upgrade to the latest release or apply the patch below.
b. The few XenForo Cloud customers still running XenForo 2.2 have been patched automatically.
The issue relates to a potential redirection exploit using a specially crafted URL.
XenForo extends thanks to @mattrogowski, @Jake B. and the team at @ThemeHouse for making us aware of this issue.
We recommend doing a full upgrade to resolve the issues, but a patch can be applied manually. See below for further details.
Locate the end of the function which currently looks like this:
Delete that entire block of code and replace with the following:
Note: If you decide to patch the files instead of doing a full upgrade, your "File health check" will report this file as having "Unexpected contents". Because these files no longer contain the same contents your version of XF was shipped with, this is expected and can be safely ignored.
As always, new releases of XenForo are free to download for all customers with active licenses, who may now grab the new version from the customer area or upgrade from your Admin control panel (Tools > Check for upgrades...).
Full details of how to install and upgrade XenForo can be found in the XenForo 2 Manual. We strongly recommend upgrading directly from within your control panel.
Cloud customers have received this patch automatically and does not require an upgrade.
Notes:
a. XenForo 2.3.1 and above is not affected by this issue. If you are still running XenForo 2.3.0 you should upgrade to the latest release or apply the patch below.
b. The few XenForo Cloud customers still running XenForo 2.2 have been patched automatically.
The issue relates to a potential redirection exploit using a specially crafted URL.
XenForo extends thanks to @mattrogowski, @Jake B. and the team at @ThemeHouse for making us aware of this issue.
We recommend doing a full upgrade to resolve the issues, but a patch can be applied manually. See below for further details.
Applying a patch manually
Method 1: manually editing the file
Applying the fix in this case requires modifying a single function within a specific file. To do so find the file src/XF/App.php and locate the start of this specific function:
PHP:
public function getDynamicRedirect($fallbackUrl = null, $useReferrer = true)
Locate the end of the function which currently looks like this:
PHP:
return $fallbackUrl;
}
Delete that entire block of code and replace with the following:
PHP:
public function getDynamicRedirect($fallbackUrl = null, $useReferrer = true)
{
if ($fallbackUrl === null)
{
$fallbackUrl = $this->router()->buildLink('index');
}
$request = $this->request();
$fallbackUrl = $request->convertToAbsoluteUri($fallbackUrl);
$redirect = $request->filter('_xfRedirect', 'str');
if (!$redirect && $useReferrer)
{
$redirect = $request->getServer('HTTP_X_AJAX_REFERER')
?: $request->getReferrer();
}
if (!$redirect || !preg_match('/./su', $redirect))
{
// no redirect provided
return $fallbackUrl;
}
if (
strpos($redirect, "\n") !== false ||
strpos($redirect, "\r") !== false ||
strpos($redirect, '@') !== false
)
{
// redirect contained newlines or user/pass
return $fallbackUrl;
}
$fullRedirect = $request->convertToAbsoluteUri($redirect);
$redirectParts = @parse_url($fullRedirect);
$redirectHost = $redirectParts['host'] ?? null;
if (!$redirectHost)
{
// no redirect host
return $fallbackUrl;
}
$requestParts = @parse_url($request->getFullBasePath());
$requestHost = $requestParts['host'] ?? null;
if ($redirectHost !== $requestHost)
{
// redirect host did not match request host
return $fallbackUrl;
}
return $fullRedirect;
}
Method 2: applying a patch/diff
You can apply the following patch to patch the file automatically:
Diff:
Index: src/XF/App.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/XF/App.php b/src/XF/App.php
--- a/src/XF/App.php (revision 7f4538e8ad4a572dcff3e0416b56906ec84a53eb)
+++ b/src/XF/App.php (revision 5a8b3ebd97eae9bc82d4f6aac5f17012d27b0043)
@@ -2305,50 +2305,85 @@
return '__' . $hash;
}
+ /**
+ * @param string|null $fallbackUrl
+ * @param bool $useReferrer
+ *
+ * @return string
+ */
public function getDynamicRedirect($fallbackUrl = null, $useReferrer = true)
{
+ if ($fallbackUrl === null)
+ {
+ $fallbackUrl = $this->router()->buildLink('index');
+ }
+
$request = $this->request();
+ $fallbackUrl = $request->convertToAbsoluteUri($fallbackUrl);
$redirect = $request->filter('_xfRedirect', 'str');
if (!$redirect && $useReferrer)
{
- $redirect = $request->getServer('HTTP_X_AJAX_REFERER');
- if (!$redirect)
- {
- $redirect = $request->getReferrer();
- }
+ $redirect = $request->getServer('HTTP_X_AJAX_REFERER')
+ ?: $request->getReferrer();
+ }
+
+ if (!$redirect || !preg_match('/./su', $redirect))
+ {
+ // no redirect provided
+ return $fallbackUrl;
}
- if ($redirect && preg_match('/./su', $redirect))
+ if (
+ strpos($redirect, "\n") !== false ||
+ strpos($redirect, "\r") !== false ||
+ strpos($redirect, '@') !== false
+ )
{
- if (strpos($redirect, "\n") === false && strpos($redirect, "\r") === false)
- {
- $fullBasePath = $request->getFullBasePath();
+ // redirect contained newlines or user/pass
+ return $fallbackUrl;
+ }
- $fullRedirect = $request->convertToAbsoluteUri($redirect);
- $redirectParts = @parse_url($fullRedirect);
- if ($redirectParts && !empty($redirectParts['host']))
- {
- $pageParts = @parse_url($fullBasePath);
+ $fullRedirect = $request->convertToAbsoluteUri($redirect);
+ $redirectParts = @parse_url($fullRedirect);
+ $redirectHost = $redirectParts['host'] ?? null;
+ if (!$redirectHost)
+ {
+ // no redirect host
+ return $fallbackUrl;
+ }
- if ($pageParts && !empty($pageParts['host']) && $pageParts['host'] == $redirectParts['host'])
- {
- return $fullRedirect;
- }
- }
- }
- }
+ $requestParts = @parse_url($request->getFullBasePath());
+ $requestHost = $requestParts['host'] ?? null;
+ if ($redirectHost !== $requestHost)
+ {
+ // redirect host did not match request host
+ return $fallbackUrl;
+ }
+
+ return $fullRedirect;
+ }
- if ($fallbackUrl === null)
+ /**
+ * @param string $notUrl
+ * @param string|null $fallbackUrl
+ * @param bool $useReferrer
+ *
+ * @return string
+ */
+ public function getDynamicRedirectIfNot(
+ $notUrl,
+ $fallbackUrl = null,
+ $useReferrer = true
+ )
+ {
+ if ($fallbackUrl === false)
{
$fallbackUrl = $this->router()->buildLink('index');
}
- return $fallbackUrl;
- }
- public function getDynamicRedirectIfNot($notUrl, $fallbackUrl = null, $useReferrer = true)
- {
$request = $this->request();
+ $fallbackUrl = $request->convertToAbsoluteUri($fallbackUrl);
$redirect = $this->getDynamicRedirect($fallbackUrl, $useReferrer);
$notUrl = $request->convertToAbsoluteUri($notUrl);
@@ -2356,17 +2391,10 @@
if (strpos($redirect, $notUrl) === 0)
{
// the URL we can't redirect to is at the start
- if ($fallbackUrl === false)
- {
- $fallbackUrl = $this->router()->buildLink('index');
- }
+ return $fallbackUrl;
+ }
- return $request->convertToAbsoluteUri($fallbackUrl);
- }
- else
- {
- return $redirect;
- }
+ return $redirect;
}
public function applyExternalDataUrl($externalPath, $canonical = false)
Note: If you decide to patch the files instead of doing a full upgrade, your "File health check" will report this file as having "Unexpected contents". Because these files no longer contain the same contents your version of XF was shipped with, this is expected and can be safely ignored.
As always, new releases of XenForo are free to download for all customers with active licenses, who may now grab the new version from the customer area or upgrade from your Admin control panel (Tools > Check for upgrades...).
One-click upgrade to XenForo 2.2.17
Directly from your admin control panelFull details of how to install and upgrade XenForo can be found in the XenForo 2 Manual. We strongly recommend upgrading directly from within your control panel.
Cloud customers have received this patch automatically and does not require an upgrade.