Skip to content

Commit e1e37fa

Browse files
authored
Merge pull request #588 from rogercgui/opac-sec
Security: Log Hardening & Input Whitelisting (is_malicious.php, log_buscas.php)
2 parents 818a73f + db843df commit e1e37fa

File tree

3 files changed

+190
-38
lines changed

3 files changed

+190
-38
lines changed
Lines changed: 129 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,165 @@
11
<?php
2-
// Function to obtain the client's real IP
2+
3+
/**
4+
* OPAC Security - Input Validation
5+
* Implements Parameter Whitelisting and Malicious Pattern Detection
6+
*/
7+
8+
// Obtain the customer's actual IP address
39
function get_client_ip()
410
{
5-
$keys = [
6-
'HTTP_CLIENT_IP',
7-
'HTTP_X_FORWARDED_FOR',
8-
'REMOTE_ADDR'
9-
];
11+
$keys = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'];
1012
foreach ($keys as $key) {
1113
if (!empty($_SERVER[$key])) {
12-
return $_SERVER[$key];
14+
// In case of multiple IPs (proxies), take the first one
15+
$ip_array = explode(',', $_SERVER[$key]);
16+
return trim($ip_array[0]);
1317
}
1418
}
1519
return 'IP_desconhecido';
1620
}
1721

18-
// Validation function against dangerous standards
22+
// Checks for dangerous patterns in VALUE (SQL Injection, XSS, Path Traversal)
1923
function is_malicious($str)
2024
{
25+
// If it is an array, do not check here (it is already handled in the validation loop).
26+
if (is_array($str)) return false;
27+
2128
$patterns = [
22-
'/<script\b/i',
23-
'/(and|or)\s+\d+=\d+/i',
24-
'/[\'"`]\s*(or|and)?\s*\d+=\d+/i',
25-
'/[\'"`]\s*--/',
26-
'/<.*?>/',
27-
'/\b(select|union|insert|delete|update|drop|eval|alert)\b/i'
29+
'/<script\b/i', // XSS
30+
'/(and|or)\s+\d+=\d+/i', // Classic SQLi (1=1)
31+
'/[\'"`]\s*(or|and)?\s*\d+=\d+/i', // SQLi with quotation marks
32+
'/union\s+select/i', // SQLi Union
33+
'/information_schema/i', // SQLi Recon
34+
'/into\s+outfile/i', // SQLi Arquivo
35+
'/\.\.\//', // Path Traversal (../)
36+
'/\.\.\\%2F/i', // Path Traversal URL Encoded
37+
'/etc\/passwd/i', // Access to system files
38+
'/cmd\.exe/i' // Command execution
2839
];
40+
2941
foreach ($patterns as $pattern) {
3042
if (preg_match($pattern, $str)) return true;
3143
}
3244
return false;
3345
}
3446

35-
36-
// Function that validates entries
47+
/**
48+
* Validates all inputs (GET/POST) against a whitelist of known parameters.
49+
* Blocks unknown keys and malicious values.
50+
*/
3751
function validate_inputs($inputs, $source = 'INPUT')
3852
{
3953
$ip = get_client_ip();
54+
55+
// 1. WHITE LIST: Only these parameters are accepted in OPAC.
56+
// Anything outside of this will be considered suspicious.
57+
$allowed_params = [
58+
// Infrastructure
59+
'base',
60+
'cipar',
61+
'ctx',
62+
'lang',
63+
'db_path',
64+
'modo',
65+
'integrada',
66+
'lista_bases',
67+
68+
// Search and Navigation
69+
'Opcion',
70+
'Expresion',
71+
'Sub_Expresion',
72+
'Sub_Expresiones',
73+
'camp',
74+
'oper',
75+
'pagina',
76+
'desde',
77+
'count',
78+
'resaltar',
79+
'Formato',
80+
'coleccion',
81+
'alcance',
82+
'prefijo',
83+
'prefijoindice',
84+
'campo',
85+
'id',
86+
'Diccio',
87+
'letra',
88+
'ira',
89+
'posting',
90+
'columnas',
91+
'facetas',
92+
'termosLivres',
93+
'search_form',
94+
95+
// Actions and User
96+
'cookie',
97+
'Accion',
98+
'sendto',
99+
'mfn',
100+
'k', // k = permalink
101+
'login',
102+
'password',
103+
'conf_level',
104+
'redirect',
105+
'existencias',
106+
107+
// Preview
108+
'titulo',
109+
'titulo_c',
110+
'submenu',
111+
'mostrar_exp',
112+
'home',
113+
'Pft',
114+
'decodificar',
115+
'page',
116+
'llamado_desde',
117+
'Navegacion',
118+
'LastKey',
119+
'Seleccionados',
120+
121+
// Session / Admin (if applicable)
122+
'sid',
123+
'token'
124+
];
125+
40126
foreach ($inputs as $key => $value) {
127+
128+
// 2. KEY VERIFICATION (PARAMETER NAME)
129+
if (!in_array($key, $allowed_params)) {
130+
// Silently attempts or blocks
131+
error_log("⚠️ [OPAC SEC] IP $ip tried unknown parameter: [$key]");
132+
133+
// To be rigid and block:
134+
die("Access Blocked: Invalid parameter detected ($key).");
135+
136+
// If you prefer to simply ignore the parameter (softer approach):
137+
// unset($_REQUEST[$key]);
138+
// continue;
139+
}
140+
141+
// 3. VALUE VERIFICATION (CONTENT)
41142
if (is_array($value)) {
143+
// Recursive validation for arrays (ex: camp[], Sub_Expresiones[])
42144
foreach ($value as $subval) {
43145
if (is_malicious($subval)) {
44-
error_log("⚠️ Blocked IP attack $ip: $source [$key] => $subval");
45-
die("Invalid input detected.<br><h1> Registered IP: $ip");
146+
error_log("⚠️ [OPAC SEC] IP $ip ataque detectado em array [$key]: $subval");
147+
die("Conteúdo inválido detectado.");
46148
}
47149
}
48150
} else {
49151
if (is_malicious($value)) {
50-
error_log("⚠️ Blocked IP attack $ip: $source [$key] => $value");
51-
die("Invalid input detected. <h1> Registered IP: $ip");
152+
error_log("⚠️ [OPAC SEC] IP $ip ataque detectado em [$key]: $value");
153+
die("Conteúdo inválido detectado.");
52154
}
53155
}
54156
}
55157
}
56158

57-
// Apply validation
58-
validate_inputs($_GET, 'GET');
59-
validate_inputs($_POST, 'POST');
60-
validate_inputs($_REQUEST, 'REQUEST');
159+
// Automatic execution on inclusion
160+
// If you want to enable it automatically, uncomment the lines below.
161+
// Otherwise, call validate_inputs($_REQUEST) at the beginning of the main scripts.
162+
/*
163+
if (!empty($_GET)) validate_inputs($_GET, 'GET');
164+
if (!empty($_POST)) validate_inputs($_POST, 'POST');
165+
*/

www/htdocs/opac/functions/log_buscas.php

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,44 +7,90 @@
77
*
88
* CHANGE LOG:
99
* 2025-10-06 rogercgui Added length limits for valid search terms.
10+
* 2025-12-11 rogercgui Added BOT detection and System/Hack filtering.
1011
*/
1112

1213
/**
1314
* Records the search term in a monthly log file.
15+
* Only records REAL searches from humans, ignoring bots and system codes.
1416
* * @param string $termo The search term to be registered.
1517
*/
1618
function registrar_log_busca($termo)
1719
{
1820
global $db_path;
1921

20-
$clean_term = str_replace(["\r", "\n"], ' ', $termo);
22+
// 1. BASIC CLEANING
23+
$clean_term = str_replace(["\r", "\n", "\t"], ' ', $termo);
2124
$clean_term = strip_tags($clean_term);
2225
$clean_term = trim($clean_term);
23-
// Sets size limits for a valid search (minimum of 2 and maximum of 255 characters).
26+
27+
// 2. SIZE VALIDATION
2428
$min_length = 2;
2529
$max_length = 255;
2630
$length_atual = mb_strlen($clean_term, 'UTF-8');
2731

28-
// FINAL CHECK: If the term is too short, too long, or empty, the function stops here.
2932
if ($length_atual < $min_length || $length_atual > $max_length) {
30-
return; // Stops execution, does NOT record the log.
33+
return;
34+
}
35+
36+
// 3. DETECTION OF ROBOTS (CRAWLERS)
37+
// If the visitor is an identified robot, we ignore the recording.
38+
$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? strtolower($_SERVER['HTTP_USER_AGENT']) : '';
39+
$bots = array(
40+
'bot',
41+
'crawl',
42+
'spider',
43+
'slurp',
44+
'facebook',
45+
'curl',
46+
'wget',
47+
'python',
48+
'java',
49+
'libwww',
50+
'httpunit',
51+
'nmap',
52+
'sqlmap'
53+
);
54+
55+
foreach ($bots as $bot) {
56+
if (strpos($user_agent, $bot) !== false) {
57+
return; // É robô, tchau!
58+
}
59+
}
60+
61+
// 4. SYSTEM JUNK FILTER AND ATTACKS
62+
// Removes searches that are clicks on facets/indexes (e.g., FUL_, NOM_) or attacks (../)
63+
// If the term begins with 3 capital letters and an underscore, it is likely a system term.
64+
if (preg_match('/^[A-Z]{2,4}_/', $clean_term)) {
65+
return; // É código interno (Facetas, Índices), não busca digitada.
3166
}
3267

33-
// The rest of the script only continues if the term is valid.
34-
$log_dir = $db_path . "/opac_conf/logs/";
68+
// Blocks Directory Traversal attempts (common attack in your logs)
69+
if (strpos($clean_term, '../') !== false || strpos($clean_term, '..%2F') !== false) {
70+
return; // Tentativa de ataque, não sujar o log de busca.
71+
}
72+
73+
// --- IF YOU'VE BEEN THROUGH ALL THAT, IT'S A VALID SEARCH ---
74+
75+
$log_dir = $db_path . "log";
76+
77+
// Checks whether the log directory exists
3578
if (!is_dir($log_dir)) {
36-
mkdir($log_dir, 0775, true);
79+
if (!mkdir($log_dir, 0777, true)) {
80+
return;
81+
}
3782
}
3883

39-
$nome_arquivo_log = "opac_" . date("Y-m") . ".log";
40-
$arquivo = $log_dir . $nome_arquivo_log;
84+
$filename = $log_dir . "/opac_" . date("Y-m") . ".log";
4185

86+
// Get the real IP (considering proxies if necessary)
4287
$ip = $_SERVER['REMOTE_ADDR'];
43-
$data = date("Y-m-d H:i:s");
88+
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
89+
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
90+
}
4491

45-
$line = "$data\t$ip\t$clean_term\n";
92+
$log_entry = date("Y-m-d H:i:s") . "\t" . $ip . "\t" . $clean_term . PHP_EOL;
4693

47-
file_put_contents($arquivo, $line, FILE_APPEND | LOCK_EX);
94+
// Write to file
95+
file_put_contents($filename, $log_entry, FILE_APPEND | LOCK_EX);
4896
}
49-
50-
?>

www/htdocs/opac/head.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
include_once(dirname(__FILE__) . "/../central/config_opac.php");
2626
include $Web_Dir . 'functions.php';
27+
validate_inputs($_REQUEST);
2728

2829
// Definition of safety headers
2930
$nonce = base64_encode(random_bytes(16));

0 commit comments

Comments
 (0)