generated from Dicken/dickendock
round33: API-Scanner Kontrollprobe gegen Geraete, die jede Anfrage einheitlich beantworten (false positives behoben)
This commit is contained in:
@@ -106,32 +106,48 @@ function looksLikeJson(body: string): boolean {
|
||||
return trimmed.startsWith("{") || trimmed.startsWith("[");
|
||||
}
|
||||
|
||||
function isApiLikeResponse(result: RawProbeResult): boolean {
|
||||
const contentTypeIsApi =
|
||||
!!result.contentType &&
|
||||
(result.contentType.includes("json") ||
|
||||
result.contentType.includes("graphql") ||
|
||||
result.contentType.includes("xml"));
|
||||
const bodyIsJson = looksLikeJson(result.bodySnippet);
|
||||
const hasAuthChallenge = !!result.wwwAuthenticate;
|
||||
return contentTypeIsApi || bodyIsJson || hasAuthChallenge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft die kuratierten Kandidaten-Pfade unter einer Basis-URL parallel und
|
||||
* liefert alle, die auf eine tatsächlich vorhandene API hindeuten:
|
||||
* - eine JSON-Antwort (egal ob 200 oder z. B. 401 "unauthorized" - eine
|
||||
* JSON-Fehlermeldung zeigt trotzdem "hier läuft eine API"),
|
||||
* - ein Content-Type, der explizit auf JSON/GraphQL/XML-API hindeutet,
|
||||
* - oder ein "WWW-Authenticate"-Header (401 mit diesem Header ist ein sehr
|
||||
* starkes Signal für eine authentifizierungspflichtige API, selbst wenn
|
||||
* der Body selbst nur eine schlichte Textmeldung ist).
|
||||
* - oder ein "WWW-Authenticate"-Header (401 mit diesem Header ist normalerweise
|
||||
* ein starkes Signal für eine authentifizierungspflichtige API).
|
||||
* Reine HTML-Seiten (z. B. eine 404-Fehlerseite des Frontends oder eine
|
||||
* Login-Weiterleitung ohne API-Signal) zählen nicht.
|
||||
*
|
||||
* VORAB ein Kontroll-Check gegen einen garantiert nicht existierenden Pfad:
|
||||
* manche Geräte (z. B. IP-Kameras) beantworten JEDE Anfrage einheitlich mit
|
||||
* z. B. Basic-Auth-Aufforderung oder demselben JSON-Fehlerobjekt - dann
|
||||
* würden buchstäblich ALLE Kandidaten-Pfade fälschlich als "API gefunden"
|
||||
* durchgehen (siehe Bugreport: eine Reolink-Türklingel "hatte" plötzlich
|
||||
* Home-Assistant-, Proxmox- UND Portainer-APIs). Reagiert das Kontroll-Pfad
|
||||
* genauso "positiv" wie ein echter Treffer, ist dem Gerät für diese Prüfung
|
||||
* grundsätzlich nicht zu trauen - dann lieber gar kein Ergebnis als 30 falsche.
|
||||
*/
|
||||
export async function detectApis(baseUrl: string): Promise<DetectedApi[]> {
|
||||
const control = await fetchRaw(`${baseUrl}/__launchpad_api_probe_${Date.now()}__`);
|
||||
if (control && control.status !== 404 && control.status !== 0 && isApiLikeResponse(control)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const checks = await Promise.all(
|
||||
CANDIDATE_PATHS.map(async ({ path, type }) => {
|
||||
const result = await fetchRaw(`${baseUrl}${path}`);
|
||||
if (!result || result.status === 0 || result.status === 404) return null;
|
||||
|
||||
const contentTypeIsApi =
|
||||
result.contentType?.includes("json") ||
|
||||
result.contentType?.includes("graphql") ||
|
||||
result.contentType?.includes("xml");
|
||||
const bodyIsJson = looksLikeJson(result.bodySnippet);
|
||||
const hasAuthChallenge = !!result.wwwAuthenticate;
|
||||
|
||||
if (!contentTypeIsApi && !bodyIsJson && !hasAuthChallenge) return null;
|
||||
if (!isApiLikeResponse(result)) return null;
|
||||
|
||||
const detected: DetectedApi = { path, type, status: result.status };
|
||||
return detected;
|
||||
|
||||
Reference in New Issue
Block a user