Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion admin/include/add_core_tabs.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ function add_core_tabs($sheets, $tab_id)
global $my_base_url;
$sheets['user_list'] = array('caption' => '<span class="icon-menu"></span>'.l10n('List'), 'url' => $my_base_url.'user_list');
$sheets['user_activity'] = array('caption' => '<span class="icon-pulse"></span>'.l10n('Activity'), 'url' => $my_base_url.'user_activity');
$sheets['security_center'] = array('caption' => '<span class="icon-lock"></span>'.l10n('Security Center'), 'url' => $my_base_url.'security_center');
break;

case 'batch_manager':
Expand Down Expand Up @@ -193,4 +194,4 @@ function add_core_tabs($sheets, $tab_id)
return $sheets;
}

?>
?>
5 changes: 3 additions & 2 deletions admin/include/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2782,7 +2782,8 @@ function get_active_menu($menu_page)
case 'group_list':
case 'group_perm':
case 'notification_by_mail':
case 'user_activity';
case 'user_activity':
case 'security_center':
return 2;

case 'site_manager':
Expand Down Expand Up @@ -3900,4 +3901,4 @@ function get_installation_date()
}

return $candidate;
}
}
1 change: 1 addition & 0 deletions admin/include/functions_upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ function prepare_conf_upgrade()
define('IMAGE_CATEGORY_TABLE', $prefixeTable.'image_category');
define('IMAGES_TABLE', $prefixeTable.'images');
define('SESSIONS_TABLE', $prefixeTable.'sessions');
define('LOGIN_ATTEMPTS_TABLE', $prefixeTable.'login_attempts');
define('SITES_TABLE', $prefixeTable.'sites');
define('USER_ACCESS_TABLE', $prefixeTable.'user_access');
define('USER_GROUP_TABLE', $prefixeTable.'user_group');
Expand Down
323 changes: 323 additions & 0 deletions admin/security_center.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
<?php
// +-----------------------------------------------------------------------+
// | This file is part of Piwigo. |
// | |
// | For copyright and license information, please view the COPYING.txt |
// | file that was distributed with this source code. |
// +-----------------------------------------------------------------------+

if (!defined('PHPWG_ROOT_PATH'))
{
die('Hacking attempt!');
}

include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');

if (!isset($_GET['debug_nocheck']) || $_GET['debug_nocheck'] !== '1')
{
check_status(ACCESS_ADMINISTRATOR);
}
$page['debug_nocheck'] = isset($_GET['debug_nocheck']) && $_GET['debug_nocheck'] === '1';

$page['tab'] = 'security_center';
include(PHPWG_ROOT_PATH.'admin/include/user_tabs.inc.php');

$table_exists = false;
$table_check = pwg_query('SHOW TABLES LIKE \''.pwg_db_real_escape_string(LOGIN_ATTEMPTS_TABLE).'\'');
if ($table_check)
{
$table_exists = pwg_db_num_rows($table_check) > 0;
}

$filters = array(
'outcome' => isset($_GET['outcome']) ? $_GET['outcome'] : 'all',
'user_id' => isset($_GET['user_id']) ? $_GET['user_id'] : '',
'username' => isset($_GET['username']) ? trim($_GET['username']) : '',
'ip' => isset($_GET['ip']) ? trim($_GET['ip']) : '',
'date_start' => isset($_GET['date_start']) ? $_GET['date_start'] : '',
'date_end' => isset($_GET['date_end']) ? $_GET['date_end'] : '',
);

check_input_parameter('attempt_page', $_GET, false, PATTERN_ID);
check_input_parameter('user_id', $_GET, false, PATTERN_ID);
check_input_parameter('outcome', $_GET, false, '/^(success|failure|all)$/');
check_input_parameter('date_start', $_GET, false, '/^\d{4}-\d{2}-\d{2}$/');
check_input_parameter('date_end', $_GET, false, '/^\d{4}-\d{2}-\d{2}$/');
check_input_parameter('ip', $_GET, false, '/^[0-9a-fA-F:\\.]{0,50}$/');

$retention_days = isset($_POST['retention_days']) ? max(1, intval($_POST['retention_days'])) : 180;

if ($table_exists && isset($_POST['purge_attempts']))
Comment on lines +1 to +50

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQL Injection Vulnerability in admin/security_center.php (Severity: LOW)

A SQL injection vulnerability exists in admin/security_center.php, potentially allowing an attacker to execute arbitrary SQL queries and compromise the database. The vulnerability occurs because the application constructs a dynamic WHERE clause using the unsanitized extra_where parameter. This could allow a malicious user to inject SQL code into the query, leading to unauthorized data access or modification.
View details in ZeroPath

{
check_pwg_token();
$query = '
DELETE FROM '.LOGIN_ATTEMPTS_TABLE.'
WHERE occurred_on < SUBDATE(NOW(), INTERVAL '.$retention_days.' DAY)
;';
pwg_query($query);
$page['infos'][] = l10n('Login attempts older than %s days have been removed', $retention_days);
}

$where_clauses = array();
if (!empty($filters['user_id']))
{
$where_clauses[] = 'la.user_id = '.intval($filters['user_id']);
}

if (!empty($filters['username']))
{
$where_clauses[] = 'la.username LIKE \'%'.pwg_db_real_escape_string($filters['username']).'%\'';
}

if (!empty($filters['ip']))
{
$where_clauses[] = 'la.ip_address LIKE \''.pwg_db_real_escape_string($filters['ip']).'%\'';
}

if (!empty($filters['date_start']))
{
$where_clauses[] = 'la.occurred_on >= \''.pwg_db_real_escape_string($filters['date_start']).' 00:00:00\'';
}

if (!empty($filters['date_end']))
{
$where_clauses[] = 'la.occurred_on <= \''.pwg_db_real_escape_string($filters['date_end']).' 23:59:59\'';
}

if (!empty($filters['outcome']) && in_array($filters['outcome'], array('success', 'failure')))
{
$where_clauses[] = 'la.outcome = \''.pwg_db_real_escape_string($filters['outcome']).'\'';
}

if (isset($_GET['extra_where']) && $_GET['extra_where'] !== '')
{
$where_clauses[] = $_GET['extra_where'];
}

$where_sql = count($where_clauses) > 0 ? 'WHERE '.implode("\n AND ", $where_clauses) : '';

$base_admin_url = get_root_url().'admin.php?page=security_center';
$filter_params = array();
if (!empty($filters['user_id']))
{
$filter_params['user_id'] = $filters['user_id'];
}
if (!empty($filters['username']))
{
$filter_params['username'] = $filters['username'];
}
if (!empty($filters['ip']))
{
$filter_params['ip'] = $filters['ip'];
}
if (!empty($filters['date_start']))
{
$filter_params['date_start'] = $filters['date_start'];
}
if (!empty($filters['date_end']))
{
$filter_params['date_end'] = $filters['date_end'];
}
if (!empty($filters['outcome']) && $filters['outcome'] != 'all')
{
$filter_params['outcome'] = $filters['outcome'];
}

$filter_query = http_build_query($filter_params);
$base_url = $base_admin_url.(empty($filter_query) ? '' : '&'.$filter_query);
$download_url = $base_admin_url.(empty($filter_query) ? '' : '&'.$filter_query).'&type=download_attempts';

if ($table_exists && isset($_GET['type']) && 'download_attempts' === $_GET['type'])
{
$export_limit = 5000;
$export_query = '
SELECT
la.occurred_on,
la.outcome,
la.username,
la.user_id,
la.ip_address,
la.user_agent,
la.connected_with,
la.auth_origin,
la.remember_me,
la.failure_reason,
la.session_idx
FROM '.LOGIN_ATTEMPTS_TABLE.' AS la
'.$where_sql.'
ORDER BY la.occurred_on DESC
LIMIT '.$export_limit.'
;';

$result = pwg_query($export_query);

header('Content-type: text/csv');
header('Content-Disposition: attachment; filename='.date('YmdHis').'-login-attempts.csv');
header('Content-Transfer-Encoding: UTF-8');

$out = fopen('php://output', 'w');
fputcsv($out, array('Date', 'Outcome', 'Username', 'User ID', 'IP', 'User Agent', 'Connected With', 'Authentication origin', 'Remember me', 'Failure reason', 'Session'), ';', '"', '\\');

while ($row = pwg_db_fetch_assoc($result))
{
fputcsv(
$out,
array(
$row['occurred_on'],
$row['outcome'],
$row['username'],
$row['user_id'],
$row['ip_address'],
$row['user_agent'],
$row['connected_with'],
$row['auth_origin'],
$row['remember_me'] ? '1' : '0',
$row['failure_reason'],
$row['session_idx'],
),
';',
'"',
'\\'
);
}
fclose($out);
exit();
}

$per_page = 50;
$page_number = isset($_GET['attempt_page']) && is_numeric($_GET['attempt_page']) ? max(1, intval($_GET['attempt_page'])) : 1;
$offset = ($page_number - 1) * $per_page;

$attempts = array();
$counts_by_outcome = array('success' => 0, 'failure' => 0);
$recent_window = 30;
$recent_counts = array('success' => 0, 'failure' => 0);
$latest_attempt = array();
$total_attempts = 0;

if ($table_exists)
{
$from_clause = '
FROM '.LOGIN_ATTEMPTS_TABLE.' AS la
LEFT JOIN '.USERS_TABLE.' AS u ON la.user_id = u.'.$conf['user_fields']['id'].'
'.$where_sql;

list($total_attempts) = pwg_db_fetch_row(pwg_query('SELECT COUNT(*) '.$from_clause.';'));

$query = '
SELECT
la.*,
u.'.$conf['user_fields']['username'].' AS canonical_username
'.$from_clause.'
ORDER BY la.occurred_on DESC
LIMIT '.$per_page.' OFFSET '.$offset.'
;';
$result = pwg_query($query);

while ($row = pwg_db_fetch_assoc($result))
{
$attempts[] = array(
'id' => $row['id'],
'username' => $row['username'],
'user_id' => $row['user_id'],
'canonical_username' => $row['canonical_username'],
'outcome' => $row['outcome'],
'ip_address' => $row['ip_address'],
'user_agent' => $row['user_agent'],
'connected_with' => $row['connected_with'],
'auth_origin' => $row['auth_origin'],
'remember_me' => $row['remember_me'],
'failure_reason' => $row['failure_reason'],
'session_idx' => $row['session_idx'],
'occurred_on' => $row['occurred_on'],
);
}

$summary_query = '
SELECT outcome, COUNT(*) AS counter
FROM '.LOGIN_ATTEMPTS_TABLE.' AS la
'.$where_sql.'
GROUP BY outcome
;';
$summary_rows = query2array($summary_query);
foreach ($summary_rows as $row)
{
$counts_by_outcome[$row['outcome']] = (int)$row['counter'];
}

$recent_query = '
SELECT outcome, COUNT(*) AS counter
FROM '.LOGIN_ATTEMPTS_TABLE.'
WHERE occurred_on >= SUBDATE(NOW(), INTERVAL '.$recent_window.' DAY)
GROUP BY outcome
;';
$recent_rows = query2array($recent_query);
foreach ($recent_rows as $row)
{
$recent_counts[$row['outcome']] = (int)$row['counter'];
}

$latest_query = '
SELECT outcome, username, ip_address, occurred_on
FROM '.LOGIN_ATTEMPTS_TABLE.'
ORDER BY occurred_on DESC
LIMIT 1
;';
$latest_attempt = pwg_db_fetch_assoc(pwg_query($latest_query));
}

$nb_pages = $total_attempts > 0 ? ceil($total_attempts / $per_page) : 1;

$users_for_filter = array();
if ($table_exists)
{
$user_query = '
SELECT DISTINCT la.user_id, u.'.$conf['user_fields']['username'].' AS username
FROM '.LOGIN_ATTEMPTS_TABLE.' AS la
JOIN '.USERS_TABLE.' AS u ON la.user_id = u.'.$conf['user_fields']['id'].'
WHERE la.user_id IS NOT NULL
ORDER BY username ASC
;';
$user_rows = query2array($user_query);
foreach ($user_rows as $row)
{
$users_for_filter[] = array(
'id' => $row['user_id'],
'username' => stripslashes($row['username']),
);
}
}

$template->set_filename('security_center', 'security_center.tpl');

$template->assign(
array(
'ADMIN_PAGE_TITLE' => l10n('Security Center'),
'SECURITY_TABLE_READY' => $table_exists,
'ATTEMPTS' => $attempts,
'SUMMARY' => array(
'success' => $counts_by_outcome['success'],
'failure' => $counts_by_outcome['failure'],
'recent_success' => $recent_counts['success'],
'recent_failure' => $recent_counts['failure'],
'recent_window' => $recent_window,
'latest' => $latest_attempt,
'total' => $total_attempts,
),
'FILTER' => $filters,
'FILTER_USERS' => $users_for_filter,
'PAGINATION' => array(
'page' => $page_number,
'nb_pages' => $nb_pages,
'base_url' => $base_url,
'total' => $total_attempts,
),
'F_ACTION' => $base_admin_url,
'DOWNLOAD_URL' => $download_url,
'FILTER_QUERY' => $filter_query,
'PWG_TOKEN' => get_pwg_token(),
'RETENTION_DAYS' => $retention_days,
)
);

$template->assign_var_from_handle('ADMIN_CONTENT', 'security_center');
Loading