Build the archlinexp.eu/www baseline from clean sources on a dedicated
site branch, per the web-baseline deploy model: vanilla Joomla 3.9.2 core
plus the developer-sourced own-code plus a vetted module layer, with the
Cadline core delta applied on the 3.9.2 base. The generator is reused
unchanged: build-baseline.sh is site-agnostic, only the content differs.
Layers:
- core/ vanilla Joomla 3.9.2 official package (installer removed)
- cadline/ own-code from the developer source (beiratkozas, maintenance,
mod_al_*, mod_alusers, mod_course_list) plus 11 assets that
the developer source lacks
- deployed/ third-party module slugs taken from live, vetted
- overrides/ the 5 genuine Cadline core modifications, on the 3.9.2 base
The live core carried 31 stale com_users files: the updater skipped the
customized ones, so the version string said 3.9.2 while the code did not.
Taking vanilla 3.9.2 plus the 2 real deltas fixes that silently.
Vetting caught two items that content-based YARA did not flag. Both come
from a structural rule: an extra file on a core-component path that is
not a template override is not a module -- it is a leftover or a plant.
Both are excluded through malware-iocs.paths:
- components/com_mailto/mail.php: an unauthenticated file-write webshell
(POST save_file + file_content -> fopen/fwrite, no auth check at all),
present on live since 2021-07-26
- administrator/components/com_joomlaupdate/restoration.php: an Akeeba
Kickstart leftover carrying a security password (known RCE vector)
Verification: build-baseline.sh -> out/ = 12284 files; YARA (16 rules) on
every source layer and on the built tree = 0 hits; none of the 291 IOC
paths present; no disguised .json dropper; core verified as 3.9.2; the
only extra file left on a core path is the legit Google reCAPTCHA library.
Assisted-by: claude-code@claude-opus-4-8
265 lines
7.2 KiB
PHP
265 lines
7.2 KiB
PHP
<?php
|
|
if (!isset($config)) require_once($_SERVER['DOCUMENT_ROOT'].'/maintenance/config.db.php');
|
|
|
|
if (!defined('_DB_HOST') && isset($config['db_host'])) define('_DB_HOST',$config['db_host']);
|
|
if (!defined('_DB_NAME') && isset($config['db_user'])) define('_DB_NAME',$config['db_user']);
|
|
if (!defined('_DB_USER') && isset($config['db_user'])) define('_DB_USER',$config['db_user']);
|
|
if (!defined('_DB_PASS') && isset($config['db_pass'])) define('_DB_PASS',$config['db_pass']);
|
|
|
|
class MySqlHelper {
|
|
public static $msh;
|
|
public static $writelog;
|
|
|
|
|
|
private $host;
|
|
private $name;
|
|
private $user;
|
|
private $pass;
|
|
|
|
private $connection;
|
|
|
|
private $queryString;
|
|
private $queryResult;
|
|
|
|
public function __construct() {
|
|
$this->connection=false;
|
|
$this->host='';
|
|
$this->user='';
|
|
$this->pass='';
|
|
$this->name='';
|
|
$this->writelog=FALSE;
|
|
}
|
|
|
|
/**
|
|
* Return the instance
|
|
*
|
|
* @return MySqlHelper
|
|
*/
|
|
public static function getInstance() {
|
|
if(!empty(self::$msh)) return self::$msh;
|
|
self::$msh=new MySqlHelper();
|
|
self::$msh->init();
|
|
return self::$msh;
|
|
}
|
|
|
|
public static function escapeString($str="")
|
|
{
|
|
$ret=mysqli_real_escape_string($this->connection,$str);
|
|
return($ret);
|
|
}
|
|
|
|
public function init($host=_DB_HOST, $name=_DB_NAME, $user=_DB_USER, $pass=_DB_PASS) {
|
|
$this->host=$host;
|
|
$this->name=$name;
|
|
$this->user=$user;
|
|
$this->pass=$pass;
|
|
}
|
|
|
|
private function connect() {
|
|
if($this->connection) return;
|
|
|
|
$this->connection=mysqli_connect($this->host, $this->user, $this->pass);
|
|
|
|
if(false===$this->connection) throw new Exception('Can\'t connect to db');
|
|
|
|
mysqli_select_db($this->connection,$this->name);
|
|
mysqli_query($this->connection, 'SET CHARACTER SET UTF8');
|
|
mysqli_query($this->connection, 'SET NAMES \'UTF8\' ');
|
|
mysqli_set_charset($this->connection, 'utf8');
|
|
|
|
if(mysqli_error($this->connection)) throw new Exception(mysqli_error($this->connection));
|
|
}
|
|
|
|
public function query($query) {
|
|
$this->connect();
|
|
$this->queryString=$query;
|
|
|
|
if ($this->is_write_type($query) === TRUE)
|
|
{
|
|
$boolWrite=TRUE;
|
|
$arrSkip=array('h_aktivalas_infok','h_history','u_history','u_userstatus_history','userstats2013','cl_hlusers.switch','cl_hlusers.writelog');
|
|
foreach ($arrSkip as $tabla) { if (strpos($query,$tabla)) {$boolWrite=FALSE;break;} }
|
|
|
|
if ($boolWrite==TRUE)
|
|
{
|
|
mysqli_query($this->connection, "INSERT INTO `cl_hlusers`.`writelog` (`user`,`datum`,`ip`,`url`,`func`,`sql`) VALUES (
|
|
'".(isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'www.archlinexp.com' )."',
|
|
'".date("Y-m-d H:i:s",time())."',
|
|
'".(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1')."',
|
|
'".(isset($_SERVER['SERVER_NAME']) ? addslashes($_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI']) : '')."',
|
|
'".addslashes("function: ".__FUNCTION__." method: ".__METHOD__)."','".addslashes($query)."');");
|
|
}
|
|
}
|
|
|
|
$this->queryResult=mysqli_query($this->connection, $query);
|
|
if(mysqli_error($this->connection)) throw new Exception(mysqli_error($this->connection).' Query: '.$this->queryString);
|
|
}
|
|
|
|
public function fetchNext() {
|
|
$back=array();
|
|
if($row=mysqli_fetch_assoc($this->queryResult)) {
|
|
foreach($row as $key => $value) {
|
|
$row[$key]=stripslashes($value);
|
|
}
|
|
$back=$row;
|
|
return $back;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function fetchAssoc() {
|
|
$back=array();
|
|
while($row=mysqli_fetch_assoc($this->queryResult)) {
|
|
foreach($row as $key => $value) {
|
|
$row[$key]=stripslashes($value);
|
|
}
|
|
$back[]=$row;
|
|
}
|
|
|
|
return $back;
|
|
}
|
|
|
|
public function getInsertedId() {
|
|
return mysqli_insert_id($this->connection);
|
|
}
|
|
|
|
public function select($table, $where=false, $offset=false, $limit=false, $order=false) {
|
|
$whereStr='';
|
|
$limitStr='';
|
|
$orderStr='';
|
|
|
|
if(is_array($where)) $whereStr='WHERE '.$this->whereMakerPriv($where);
|
|
elseif($where) $whereStr='WHERE '.$where;
|
|
|
|
if(($offset!==false and $offset!==null)&&($limit!==false and $limit!==null)) {
|
|
$limitStr='LIMIT '.$offset.', '.$limit;
|
|
}
|
|
|
|
if(is_array($order)) {
|
|
$orderStr='ORDER BY ';
|
|
foreach($order as $key => $value) {
|
|
$orderStr.=$key.' '.$value.' ';
|
|
}
|
|
}
|
|
elseif($order) $orderStr='ORDER BY '.$order;
|
|
|
|
return $this->query('SELECT * FROM '.$table.' '.$whereStr.' '.$orderStr.' '.$limitStr);
|
|
}
|
|
|
|
public function getProp($table, $func, $field, $where=false) {
|
|
$whereStr='';
|
|
if(is_array($where)) $whereStr='WHERE '.$this->whereMakerPriv($where);
|
|
elseif($where) $whereStr='WHERE '.$where;
|
|
|
|
$this->query('SELECT '.$func.'('.$field.') as p FROM '.$table.' '.$whereStr);
|
|
|
|
$arr=$this->fetchAssoc();
|
|
|
|
return $arr[0]['p'];
|
|
}
|
|
|
|
public function getCount($table, $field='id', $where=false) {
|
|
return $this->getProp($table, 'COUNT', $field, $where);
|
|
}
|
|
|
|
public function getSum($table, $field='id', $where=false) {
|
|
return $this->getProp($table, 'SUM', $field, $where);
|
|
}
|
|
|
|
public function getAvg($table, $field='id', $where=false) {
|
|
return $this->getProp($table, 'AVG', $field, $where);
|
|
}
|
|
|
|
public function delete($table, $where=false) {
|
|
$whereStr='';
|
|
if(is_array($where)) $whereStr='WHERE '.$this->whereMakerPriv($where);
|
|
elseif($where) $whereStr='WHERE '.$where;
|
|
|
|
return $this->query('DELETE FROM '.$table.' '.$whereStr);
|
|
}
|
|
|
|
public function update($table, $infoArr, $where=false) {
|
|
if(!is_array($infoArr)) {
|
|
return false;
|
|
}
|
|
|
|
$whereStr='';
|
|
$dataArr=array();
|
|
$data='';
|
|
if(is_array($where)) $whereStr='WHERE '.$this->whereMakerPriv($where);
|
|
elseif($where) $whereStr='WHERE '.$where;
|
|
|
|
foreach($infoArr as $key => $value) {
|
|
$dataArr[]='`'.$key.'`="'.mysqli_real_escape_string($this->connection,$value).'" ';
|
|
}
|
|
$data=implode(',',$dataArr);
|
|
|
|
return $this->query('UPDATE '.$table.' SET '.$data.' '.$whereStr);
|
|
}
|
|
|
|
public function insert($table, $infoArr) {
|
|
$this->connect();
|
|
$columArr=array();
|
|
$valueArr=array();
|
|
$colums='';
|
|
$values='';
|
|
|
|
foreach($infoArr as $key => $value) {
|
|
$columArr[]='`'.$key.'`';
|
|
$valueArr[]='"'.mysqli_real_escape_string($this->connection, $value).'"';
|
|
}
|
|
|
|
$colums=implode(',',$columArr);
|
|
$values=implode(',', $valueArr);
|
|
|
|
return $this->query('INSERT INTO '.$table.' ('.$colums.') VALUES ('.$values.');');
|
|
}
|
|
|
|
public function affectedRows() {
|
|
return mysqli_affected_rows($this->connection);
|
|
}
|
|
|
|
public function whereMakerPriv($where) {
|
|
if(!is_array($where)) return 1;
|
|
$this->connect();
|
|
$back='1 ';
|
|
foreach($where as $key=>$value) {
|
|
if(is_array($value)) $back.='AND `'.$key.'`'.$value[0].'"'.$value[1].'" ';
|
|
else $back.='AND `'.$key.'`="'.mysqli_real_escape_string($this->connection,$value).'" ';
|
|
}
|
|
return $back;
|
|
}
|
|
|
|
public static function whereMaker($where) {
|
|
if(!is_array($where)) return 1;
|
|
$back='1 ';
|
|
foreach($where as $key=>$value) {
|
|
if(is_array($value)) $back.='AND `'.$key.'`'.$value[0].'"'.$value[1].'" ';
|
|
else $back.='AND `'.$key.'`="'.$value.'" ';
|
|
}
|
|
return $back;
|
|
}
|
|
|
|
public function is_write_type($sql)
|
|
{
|
|
if ( ! preg_match('/^\s*"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD DATA|COPY|ALTER|GRANT|REVOKE|LOCK|UNLOCK)\s+/i', $sql))
|
|
{
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
public function getConnection()
|
|
{
|
|
return ($this->connection);
|
|
}
|
|
|
|
public function setConnect()
|
|
{
|
|
$this->connect();
|
|
}
|
|
|
|
|
|
}
|
|
?>
|