fix: Audit-Findings — en_US-Parity, LIKE-Escape, Rate-Limit [deploy]
All checks were successful
Deploy Eplan / deploy (push) Successful in 9s
All checks were successful
Deploy Eplan / deploy (push) Successful in 9s
Aus /dolibarr audit Report: - en_US: 5 fehlende Tab-/Placeholder-Keys ergänzt (Parity 100%) - LIKE-Wildcard-Escape: %, _ und \\ im User-Input werden maskiert bevor in LIKE '%..%'-Pattern eingebaut (sonst matched "100%" zu viel) Betrifft auftraege_listen (Z.75-77) und kunden_suchen (Z.124) - Rate-Limit: File-basiert nach KB #354 — max. 10 fehlgeschlagene Token- Checks pro IP in 15 Minuten, dann 429. Bei Erfolg wird Zähler resettet. Zähler liegen unter DOL_DATA_ROOT/eplan/loginattempts/<sha1(ip)>.json Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cf0b78893c
commit
8930d7804a
3 changed files with 53 additions and 2 deletions
|
|
@ -36,12 +36,19 @@ header('Content-Type: application/json; charset=utf-8');
|
||||||
header('Cache-Control: no-store');
|
header('Cache-Control: no-store');
|
||||||
|
|
||||||
// --- Auth ---
|
// --- Auth ---
|
||||||
|
if (!eplanRateLimit()) {
|
||||||
|
http_response_code(429);
|
||||||
|
echo json_encode(['error' => 'Too Many Requests', 'detail' => 'Zu viele fehlgeschlagene Versuche. Warte 15 Minuten.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
$token = eplanTokenAusRequest();
|
$token = eplanTokenAusRequest();
|
||||||
if (!eplanTokenPruefen($token)) {
|
if (!eplanTokenPruefen($token)) {
|
||||||
|
eplanRateLimit(false); // Fehlversuch zählen
|
||||||
http_response_code(401);
|
http_response_code(401);
|
||||||
echo json_encode(['error' => 'Unauthorized', 'detail' => 'Ungültiger oder fehlender Token']);
|
echo json_encode(['error' => 'Unauthorized', 'detail' => 'Ungültiger oder fehlender Token']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
eplanRateLimit(true); // Erfolg → Zähler zurücksetzen
|
||||||
|
|
||||||
if (!isModEnabled('eplan')) {
|
if (!isModEnabled('eplan')) {
|
||||||
http_response_code(503);
|
http_response_code(503);
|
||||||
|
|
@ -71,7 +78,8 @@ try {
|
||||||
LEFT JOIN ".MAIN_DB_PREFIX."societe t ON t.rowid = c.fk_soc
|
LEFT JOIN ".MAIN_DB_PREFIX."societe t ON t.rowid = c.fk_soc
|
||||||
WHERE c.entity IN (".getEntity('commande').")";
|
WHERE c.entity IN (".getEntity('commande').")";
|
||||||
if (!empty($such)) {
|
if (!empty($such)) {
|
||||||
$such_esc = $db->escape($such);
|
// LIKE-Wildcards im User-Input escapen, dann erst SQL-escape
|
||||||
|
$such_esc = $db->escape(str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $such));
|
||||||
$sql .= " AND (c.ref LIKE '%".$such_esc."%'
|
$sql .= " AND (c.ref LIKE '%".$such_esc."%'
|
||||||
OR c.ref_client LIKE '%".$such_esc."%'
|
OR c.ref_client LIKE '%".$such_esc."%'
|
||||||
OR t.nom LIKE '%".$such_esc."%')";
|
OR t.nom LIKE '%".$such_esc."%')";
|
||||||
|
|
@ -118,7 +126,7 @@ try {
|
||||||
case 'kunden_suchen':
|
case 'kunden_suchen':
|
||||||
$such = GETPOST('such', 'alphanohtml');
|
$such = GETPOST('such', 'alphanohtml');
|
||||||
if (empty($such) || strlen($such) < 2) { echo json_encode([]); break; }
|
if (empty($such) || strlen($such) < 2) { echo json_encode([]); break; }
|
||||||
$such_esc = $db->escape($such);
|
$such_esc = $db->escape(str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $such));
|
||||||
$sql = "SELECT rowid as id, nom, code_client FROM ".MAIN_DB_PREFIX."societe
|
$sql = "SELECT rowid as id, nom, code_client FROM ".MAIN_DB_PREFIX."societe
|
||||||
WHERE entity IN (".getEntity('societe').")
|
WHERE entity IN (".getEntity('societe').")
|
||||||
AND (nom LIKE '%".$such_esc."%' OR code_client LIKE '%".$such_esc."%')
|
AND (nom LIKE '%".$such_esc."%' OR code_client LIKE '%".$such_esc."%')
|
||||||
|
|
|
||||||
|
|
@ -39,3 +39,10 @@ EplanFeature2 = Bluetooth laser (Bosch GLM) for precise measurements
|
||||||
EplanFeature3 = Offline capable — works without internet
|
EplanFeature3 = Offline capable — works without internet
|
||||||
EplanFeature4 = PDF and DXF export (CAD compatible)
|
EplanFeature4 = PDF and DXF export (CAD compatible)
|
||||||
EplanFeature5 = Measure multiple rooms per project
|
EplanFeature5 = Measure multiple rooms per project
|
||||||
|
|
||||||
|
# Tabs
|
||||||
|
Aufmaß = Measurement
|
||||||
|
EplanStartAufmass = Start measurement
|
||||||
|
EplanKeinePwa = ElektroPlan PWA URL not configured.
|
||||||
|
EplanKeineAufmasse = No measurements linked yet. Start a new measurement via the button above.
|
||||||
|
EplanKeineAuftraege = No orders for this customer.
|
||||||
|
|
|
||||||
|
|
@ -61,3 +61,39 @@ function eplanTokenAusRequest()
|
||||||
if (!empty($header)) return $header;
|
if (!empty($header)) return $header;
|
||||||
return $_GET['token'] ?? $_POST['token'] ?? null;
|
return $_GET['token'] ?? $_POST['token'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File-basierter Brute-Force-Schutz (KB #354):
|
||||||
|
* Pro IP maximal 10 fehlgeschlagene Versuche in 15 Minuten, danach blockiert.
|
||||||
|
* Bei Erfolg wird der Zähler zurückgesetzt.
|
||||||
|
* @return bool true wenn Request erlaubt, false wenn geblockt
|
||||||
|
*/
|
||||||
|
function eplanRateLimit($erfolg = null)
|
||||||
|
{
|
||||||
|
global $conf;
|
||||||
|
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||||
|
$dir = DOL_DATA_ROOT.'/eplan/loginattempts';
|
||||||
|
if (!is_dir($dir)) @mkdir($dir, 0755, true);
|
||||||
|
$datei = $dir.'/'.sha1($ip).'.json';
|
||||||
|
$now = time();
|
||||||
|
$fenster = 900; // 15 Minuten
|
||||||
|
$limit = 10;
|
||||||
|
|
||||||
|
$daten = ['count' => 0, 'first' => $now];
|
||||||
|
if (file_exists($datei)) {
|
||||||
|
$daten = json_decode(file_get_contents($datei), true) ?: $daten;
|
||||||
|
if ($now - ($daten['first'] ?? 0) > $fenster) {
|
||||||
|
$daten = ['count' => 0, 'first' => $now];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($erfolg === true) {
|
||||||
|
@unlink($datei);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ($erfolg === false) {
|
||||||
|
$daten['count']++;
|
||||||
|
@file_put_contents($datei, json_encode($daten));
|
||||||
|
}
|
||||||
|
return ($daten['count'] ?? 0) < $limit;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue