Merge pull request #6911 from JediKev/security/latest-patches-01-2026

Reviewed-By: JediKev <kevin@enhancesoft.com>
This commit is contained in:
JediKev
2026-01-15 11:01:28 -06:00
committed by GitHub
9 changed files with 51 additions and 24 deletions

View File

@@ -68,7 +68,7 @@ if($thisclient && $thisclient->getId() && $thisclient->isValid()){
/******* CSRF Protectin *************/
// Enforce CSRF protection for POSTS
if ($_POST && !$ost->checkCSRFToken()) {
if ($_SERVER['REQUEST_METHOD'] == 'POST' && !$ost->checkCSRFToken()) {
Http::redirect('index.php');
//just incase redirect fails
die('Action denied (400)!');

View File

@@ -278,6 +278,11 @@ class Format {
$props = array();
foreach ($styles as $i=>&$s) {
@list($prop, $val) = explode(':', $s);
// Strip all CSS url() usages in inline styles
if (preg_match('/\burl\s*\(/i', $val)) {
unset($styles[$i]);
continue;
}
if (isset($props[$prop])) {
unset($styles[$i]);
continue;
@@ -348,7 +353,7 @@ class Format {
'comment' => 1, //Remove html comments (OUTLOOK LOVE THEM)
'tidy' => -1,
'elements' => '*-form-input-button',
'deny_attribute' => 'id, formaction, action, on*',
'deny_attribute' => 'id, formaction, action, srcset, on*',
'schemes' => 'href: aim, feed, file, ftp, gopher, http, https, irc, mailto, news, nntp, sftp, ssh, telnet; *:file, http, https; src: cid, http, https, data',
'hook_tag' => function($e, $a=0) { return Format::__html_cleanup($e, $a); },
);
@@ -357,9 +362,12 @@ class Format {
if ($cfg)
$whitelist = $cfg->getIframeWhitelist();
if (!empty($whitelist)) {
$whitelist_quoted = array_map(function ($d) {
return preg_quote($d, '`');
}, $whitelist);
$config['elements'] .= '+iframe';
$config['spec'] = 'iframe=-*,height,width,type,style,src(match="`^(https?:)?//(www\.)?('
.implode('|', $whitelist)
.implode('|', $whitelist_quoted)
.')(\?|/|#)([^@]*)$`i"),frameborder'.($options['spec'] ? '; '.$options['spec'] : '').',allowfullscreen';
}
@@ -483,16 +491,17 @@ class Format {
// Allowed Inline External Image Extensions
$allowed = array('gif', 'png', 'jpg', 'jpeg');
$exclude = !$cfg->allowExternalImages();
$local = false;
$input = preg_replace_callback('/<img ([^>]*?)(src="([^"]+)")([^>]*)\/?>/',
function($match) use ($local, $allowed, $exclude, $display) {
if (strpos($match[3], 'cid:') !== false)
$local = true;
$input = preg_replace_callback('/<img\b([^>]*?)\bsrc\s*=\s*(["\'])(.*?)\2([^>]*)\/?>/i',
function($match) use ($allowed, $exclude, $display) {
$src = $match[3];
// CID is only valid if it is the prefix of the src value
$local = (stripos(ltrim($src), 'cid:') === 0);
// Split the src URL and get the extension
$part = parse_url($match[3], PHP_URL_PATH);
$path = explode('.', $part);
$part = parse_url($src, PHP_URL_PATH);
$path = explode('.', (string) $part);
$ext = preg_split('/[^A-Za-z]/', end($path))[0];
if (!$local && (($exclude && $display) || !in_array($ext, $allowed)))
@@ -500,7 +509,8 @@ class Format {
else
return $match[0];
},
$input);
$input
);
return $input;
}

View File

@@ -5248,6 +5248,8 @@ class FileUploadWidget extends Widget {
);
function render($options=array()) {
global $ost;
$config = $this->field->getConfiguration();
$name = $this->field->getFormName();
$id = substr(md5(spl_object_hash($this)), 10);
@@ -5333,6 +5335,7 @@ class FileUploadWidget extends Widget {
url: 'ajax.php/form/upload/<?php echo $field_id; ?>',
link: $('#<?php echo $id; ?>').find('a.manual'),
paramname: 'upload[]',
data: {"__CSRFToken__": "<?php echo $ost->getCSRF()->getToken(); ?>"},
fallback_id: 'file-<?php echo $id; ?>',
allowedfileextensions: <?php echo JsonDataEncoder::encode(
$config['__extensions'] ?: array()); ?>,

View File

@@ -422,7 +422,7 @@ class DatabaseSessionRecord extends VerySimpleModel
}
catch (DoesNotExist $e) {
// We're auto-creating model (unsaved) when one doesn't exist?
$record = $autocreate ? self::create($id) : null;
$record = ($autocreate && ctype_alnum($id)) ? self::create($id) : null;
}
catch (OrmException | Exception $ex) {
// This could happen if more than one record exits in the

View File

@@ -26,29 +26,32 @@ class mPDFWithLocalImages extends Mpdf {
$self = $this;
$images = $cids = array();
// Try and get information for all the files in one query
if (preg_match_all('/"cid:([\w._-]{32})"/', $html, $cids)) {
if (preg_match_all('/\bsrc\s*=\s*(["\'])\s*cid:\s*([\w._-]{32})\s*\1/i', $html, $cids)) {
foreach (AttachmentFile::objects()
->filter(array('key__in' => $cids[1]))
->filter(array('key__in' => $cids[2]))
as $file
) {
$images[strtolower($file->getKey())] = $file;
}
}
$args[0] = preg_replace_callback('/"cid:([\w.-]{32})"/',
$args[0] = preg_replace_callback('/\bsrc\s*=\s*(["\'])\s*cid:\s*([\w._-]{32})\s*\1/i',
function($match) use ($self, $images, &$filenumber) {
if (!($file = @$images[strtolower($match[1])]))
return $match[0];
if (!($file = @$images[strtolower($match[2])]))
return $match[0]; // leave unchanged if not resolvable
$key = "__attached_file_".$filenumber++;
$self->imageVars[$key] = $file->getData();
return 'var:'.$key;
// Preserve attribute formatting: src="var:..."
return 'src='.$match[1].'var:'.$key.$match[1];
},
$html
);
// unregister phar stream to mitigate vulnerability in mpdf library
@stream_wrapper_unregister('phar');
@stream_wrapper_unregister('php');
call_user_func_array(array('parent', 'WriteHtml'), $args);
// restore phar stream
@stream_wrapper_restore('phar');
@stream_wrapper_restore('php');
}
function output($name = '', $dest = '') {

View File

@@ -229,9 +229,9 @@ class Validator {
return $error == '';
}
static function is_userid($userid, &$error='') {
static function is_userid($userid, &$error='', $verify=false) {
if (!self::is_username($userid)
&& !self::is_email($userid))
&& !self::is_email($userid, false, $verify))
$error = __('Invalid User Id ');
return $error == '';
}

View File

@@ -48,8 +48,9 @@ if ($_POST) {
if ($_POST && isset($_POST['luser'])) {
if (!$_POST['luser'])
$errors['err'] = __('Valid username or email address is required');
elseif (($user = UserAuthenticationBackend::process(trim($_POST['luser']),
substr($_POST['lpasswd'], 0, 128), $errors))) {
elseif (Validator::is_userid(trim($_POST['luser']), $errors['err'], false)
&& ($user = UserAuthenticationBackend::process(trim($_POST['luser']),
substr($_POST['lpasswd'], 0, 128), $errors))) {
if ($user instanceof ClientCreateRequest) {
if ($cfg && $cfg->isClientRegistrationEnabled()) {
// Attempt to automatically register

View File

@@ -15,6 +15,7 @@ if($_POST) {
}
switch ($_POST['do']) {
case 'sendmail':
$start = microtime(true);
$userid = (string) $_POST['userid'];
if (Validator::is_userid($userid)
&& ($acct=ClientAccount::lookupByUsername($userid))) {
@@ -34,6 +35,14 @@ if($_POST) {
else
$inc = 'pwreset.sent.php';
$min = 1.4;
$jitter = random_int(0, 250) / 1000;
$target = $min + $jitter;
$elapsed = microtime(true) - $start;
if ($elapsed < $target)
usleep((int)(($target - $elapsed) * 1_000_000));
break;
case 'reset':
$inc = 'pwreset.login.php';

View File

@@ -68,8 +68,9 @@ if ($_POST) {
if ($_POST && isset($_POST['userid'])) {
// Lookup support backends for this staff
$username = trim($_POST['userid']);
if ($user = StaffAuthenticationBackend::process($username,
substr($_POST['passwd'], 0, 128), $errors)) {
if (Validator::is_userid($username, $errors['err'], false)
&& ($user = StaffAuthenticationBackend::process($username,
substr($_POST['passwd'], 0, 128), $errors))) {
$redirect($user->isValid() ? $dest : 'login.php');
}