1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
<?php
class Audit
{
/** @var ?int */
private static $overrideResponseCode = null;
/**
* Logs the current POST parameters to the audit table, applying filtering to sensistive data.
*
* @param string $module The name of the module being executed.
*/
public static function run(string $module): void
{
$maxTotalLen = User::isLoggedIn() ? 1000000 : 10000;
$hadLongString = true;
$filtered = null;
for ($maxlen = 1000; $hadLongString && $maxlen > 100; $maxlen -= 100) {
$hadLongString = false;
$filtered = self::processPostArray($_POST, $maxlen, $hadLongString);
$filtered = json_encode($filtered);
if (strlen($filtered) < $maxTotalLen)
break;
$filtered = null;
}
$data = [
'dateline' => time(),
'userid' => User::getId(),
'ipaddr' => $_SERVER['REMOTE_ADDR'] ?? 'cli',
'module' => $module,
'action' => $_REQUEST['action'] ?? '',
'data' => $filtered ?? 'EXCESS',
];
$ret = Database::exec('INSERT IGNORE INTO audit (dateline, userid, ipaddr, module, action, data)
VALUES (:dateline, :userid, :ipaddr, :module, :action, :data)', $data, true);
if ($ret) {
$rowId = Database::lastInsertId();
register_shutdown_function(function() use ($rowId) {
$code = self::$overrideResponseCode ?? http_response_code();
if ($code) {
Database::exec("UPDATE IGNORE audit SET response = :code WHERE id = :id",
['id' => $rowId, 'code' => $code], true);
}
});
}
if ($filtered === null)
ErrorHandler::traceError('POST payload exceeded limit');
}
/**
* Process the provided (POST)array recursively, applying filters and truncation as needed.
*
* @param array $array The array to process
* @param int $maxlen The maximum length allowed for strings
* @param bool &$hadLongString A reference variable to track if a long string was encountered
* @return array The processed array with filtered and shortened values
*/
private static function processPostArray(array $array, int $maxlen, bool &$hadLongString): array
{
$filtered = [];
foreach ($array as $key => $value) {
if ($key === 'prevent_autofill' || $key === 'password_fake'
|| $key === 'do' || $key === 'action' || $key === 'token')
continue; // These don't matter
$lkey = strtolower($key);
if (strpos($lkey, 'pass') !== false || strpos($lkey, 'token') !== false
|| substr($lkey, -3) === 'key' // privatekey, hmac-key, ...
|| substr($lkey, 0, 2) === 'pw') {
$value = '*****'; // Censor
} elseif (is_array($value)) {
$value = self::processPostArray($value, $maxlen, $hadLongString);
} elseif (mb_strlen($value) > $maxlen) {
// Shorten
$hadLongString = true;
$value = mb_substr($value, 0, $maxlen) . '...';
}
if (!empty($value)) {
$filtered[$key] = $value;
}
}
return $filtered;
}
/**
* Sets a custom HTTP response code to be used for logging in the audit table.
*
* @param int $responseCode The custom HTTP response code to be set.
*/
public static function overrideResponseCode(int $responseCode, bool $replaceExisting = true): void
{
if ($replaceExisting || self::$overrideResponseCode === null) {
self::$overrideResponseCode = $responseCode;
}
}
}
|