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("[");
|
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
|
* Prüft die kuratierten Kandidaten-Pfade unter einer Basis-URL parallel und
|
||||||
* liefert alle, die auf eine tatsächlich vorhandene API hindeuten:
|
* liefert alle, die auf eine tatsächlich vorhandene API hindeuten:
|
||||||
* - eine JSON-Antwort (egal ob 200 oder z. B. 401 "unauthorized" - eine
|
* - eine JSON-Antwort (egal ob 200 oder z. B. 401 "unauthorized" - eine
|
||||||
* JSON-Fehlermeldung zeigt trotzdem "hier läuft eine API"),
|
* JSON-Fehlermeldung zeigt trotzdem "hier läuft eine API"),
|
||||||
* - ein Content-Type, der explizit auf JSON/GraphQL/XML-API hindeutet,
|
* - ein Content-Type, der explizit auf JSON/GraphQL/XML-API hindeutet,
|
||||||
* - oder ein "WWW-Authenticate"-Header (401 mit diesem Header ist ein sehr
|
* - oder ein "WWW-Authenticate"-Header (401 mit diesem Header ist normalerweise
|
||||||
* starkes Signal für eine authentifizierungspflichtige API, selbst wenn
|
* ein starkes Signal für eine authentifizierungspflichtige API).
|
||||||
* der Body selbst nur eine schlichte Textmeldung ist).
|
|
||||||
* Reine HTML-Seiten (z. B. eine 404-Fehlerseite des Frontends oder eine
|
* Reine HTML-Seiten (z. B. eine 404-Fehlerseite des Frontends oder eine
|
||||||
* Login-Weiterleitung ohne API-Signal) zählen nicht.
|
* 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[]> {
|
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(
|
const checks = await Promise.all(
|
||||||
CANDIDATE_PATHS.map(async ({ path, type }) => {
|
CANDIDATE_PATHS.map(async ({ path, type }) => {
|
||||||
const result = await fetchRaw(`${baseUrl}${path}`);
|
const result = await fetchRaw(`${baseUrl}${path}`);
|
||||||
if (!result || result.status === 0 || result.status === 404) return null;
|
if (!result || result.status === 0 || result.status === 404) return null;
|
||||||
|
if (!isApiLikeResponse(result)) 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;
|
|
||||||
|
|
||||||
const detected: DetectedApi = { path, type, status: result.status };
|
const detected: DetectedApi = { path, type, status: result.status };
|
||||||
return detected;
|
return detected;
|
||||||
|
|||||||
Reference in New Issue
Block a user