Dovresti proteggerti da valori imprevisti da API esterne?


51

Supponiamo che stai codificando una funzione che accetta input da un'API esterna MyAPI.

L'API esterna MyAPIha un contratto che afferma che restituirà a stringo a number.

E 'consigliato a guardia contro le cose come null, undefined, boolean, ecc anche se non fa parte delle API di MyAPI? In particolare, dal momento che non hai alcun controllo su quell'API, non puoi fornire la garanzia attraverso qualcosa come l'analisi del tipo statico, quindi è meglio prevenire che curare?

Sto pensando in relazione al principio di robustezza .


16
Quali sono gli impatti di non gestire quei valori imprevisti se vengono restituiti? Riesci a vivere con questi impatti? Vale la pena di gestire quei valori inaspettati per evitare di dover affrontare gli impatti?
Vincent Savard

55
Se li aspetti, per definizione non sono inaspettati.
Mason Wheeler

28
Ricorda che l'API non è obbligata a restituirti un JSON valido (suppongo che sia JSON). Puoi anche ottenere una risposta simile a<!doctype html><html><head><title>504 Gateway Timeout</title></head><body>The server was unable to process your request. Make sure you have typed the address correctly. If the problem persists, please try again later.</body></html>
user253751

5
Che cosa significa "API esterna"? È ancora sotto il tuo controllo?
Deduplicatore

11
"Un buon programmatore è qualcuno che guarda in entrambi i modi prima di attraversare una strada a senso unico."
jeroen_de_schutter

Risposte:


103

Non dovresti mai fidarti degli input per il tuo software, indipendentemente dalla fonte. Non solo è importante convalidare i tipi, ma anche intervalli di input e anche la logica aziendale. Per un commento, questo è ben descritto da OWASP

In caso contrario, nella migliore delle ipotesi vi lasceremo con i dati spazzatura che dovrete successivamente ripulire, ma nella peggiore delle ipotesi lascerete un'opportunità per exploit dannosi se quel servizio a monte viene compromesso in qualche modo (qv l'hack Target). La gamma di problemi nel mezzo include il ripristino dell'applicazione in uno stato irrecuperabile.


Dai commenti posso vedere che forse la mia risposta potrebbe usare un po 'di espansione.

Per "non fidarti mai degli input", intendo semplicemente che non puoi presumere che riceverai sempre informazioni valide e affidabili da sistemi a monte o a valle, e quindi dovresti sempre disinfettare quell'input al meglio delle tue capacità, o rifiutare esso.

Un argomento emerso nei commenti che affronterò a titolo di esempio. Sebbene sì, devi fidarti del tuo sistema operativo in una certa misura, non è irragionevole, ad esempio, rifiutare i risultati di un generatore di numeri casuali se gli chiedi un numero compreso tra 1 e 10 e risponde con "bob".

Analogamente, nel caso dell'OP, è necessario assicurarsi che l'applicazione accetti solo input validi dal servizio upstream. Quello che fai quando non va bene dipende da te e dipende molto dall'effettiva funzione aziendale che stai cercando di realizzare, ma minimamente lo registreresti per il debug successivo e in caso contrario assicureresti che la tua applicazione non vada in uno stato irrecuperabile o insicuro.

Anche se non puoi mai sapere ogni possibile input che qualcuno / qualcosa potrebbe darti, puoi certamente limitare ciò che è consentito in base ai requisiti aziendali e fare una sorta di whitelist di input basato su quello.


20
Cosa significa qv?
JonH

15
@JonH sostanzialmente "vedi anche" ... l'hack Target è un esempio a cui fa riferimento en.oxforddictionaries.com/definition/qv .
Andrew

8
Questa risposta è così com'è, non ha senso. È impossibile prevedere tutti i modi in cui una libreria di terze parti potrebbe comportarsi in modo errato. Se la documentazione di una funzione di libreria assicura esplicitamente che il risultato avrà sempre alcune proprietà, allora dovresti essere in grado di fare affidamento su di esso che i progettisti si sono assicurati che questa proprietà avrebbe effettivamente conservato. È loro responsabilità disporre di una suite di test che controlli questo tipo di cose e invii una correzione di bug nel caso in cui si verifichi una situazione in cui non esiste. Si verifica di queste proprietà nel proprio codice sta violando il principio DRY.
lasciato il

23
@leftaroundabout no, ma dovresti essere in grado di prevedere tutte le cose valide che la tua richiesta può accettare e rifiutare il resto.
Paul,

10
@leftaroundabout Non si tratta di diffidare di tutto, si tratta di diffidare di fonti esterne non attendibili. Si tratta solo di modellare le minacce. Se non hai fatto in modo che il tuo software non sia sicuro (come può essere, se non hai mai pensato a quale tipo di attori e minacce vuoi proteggere la tua applicazione?). Per una serie di software aziendali è un valore predefinito ragionevole supporre che i chiamanti possano essere dannosi, mentre raramente è ragionevole presumere che il sistema operativo sia una minaccia.
Voo

33

certo. Ma cosa ti fa pensare che la risposta potrebbe essere diversa?

Sicuramente non vuoi lasciare che il tuo programma si comporti in modo imprevedibile nel caso in cui l'API non restituisca ciò che dice il contratto, vero? Quindi almeno devi affrontare un simile comportamento in qualche modo . Una forma minima di gestione degli errori vale sempre lo sforzo (molto minimo!) E non ci sono assolutamente scuse per non implementare qualcosa del genere.

Tuttavia, quanto sforzo dovresti investire per affrontare un caso del genere dipende fortemente dal caso e può essere risolto solo nel contesto del tuo sistema. Spesso può essere sufficiente una breve registrazione e lasciare che l'applicazione termini correttamente. A volte, sarà meglio implementare una gestione dettagliata delle eccezioni, gestendo diverse forme di valori di ritorno "errati" e forse sarà necessario implementare una strategia di fallback.

Ma fa davvero la differenza se stai scrivendo solo un'applicazione di formattazione di fogli di calcolo interna, da utilizzare da meno di 10 persone e in cui l'impatto finanziario di un incidente dell'applicazione è piuttosto basso, o se stai creando una nuova guida autonoma in auto sistema, in cui un arresto anomalo dell'applicazione può costare la vita.

Quindi non ci sono scorciatoie per riflettere su ciò che stai facendo , usare il tuo buon senso è sempre obbligatorio.


Cosa fare è un'altra decisione. Potresti avere una soluzione di failover. Qualsiasi cosa asincrona può essere ritentata prima di creare un registro delle eccezioni (o lettera morta). Un avviso attivo al fornitore o al fornitore può essere un'opzione se il problema persiste.
mckenzm,

@mckenzm: il fatto che il PO faccia una domanda in cui la risposta letterale può ovviamente essere solo "sì" è un segno dell'IMHO che potrebbero non essere interessati solo a una risposta letterale. Sembra che stiano chiedendo "è necessario evitare diverse forme di valori imprevisti da un'API e gestirli in modo diverso" ?
Doc Brown,

1
hmm, l'approccio merda / carpa / die. È colpa nostra se abbiamo inviato richieste sbagliate (ma legali)? la risposta è possibile, ma non utilizzabile per noi in particolare? o la risposta è corrotta? Scenari diversi, ora sembra fare i compiti.
mckenzm,

21

Il principio di robustezza - in particolare, "sii liberale in ciò che accetti" metà di esso - è una pessima idea nel software. È stato originariamente sviluppato nel contesto dell'hardware, dove i vincoli fisici rendono le tolleranze ingegneristiche molto importanti, ma nel software, quando qualcuno ti invia input non corretto o altrimenti improprio, hai due scelte. Puoi rifiutarlo (preferibilmente con una spiegazione di cosa è andato storto) oppure puoi provare a capire cosa avrebbe dovuto significare.

EDIT: Si scopre che mi sono sbagliato nella dichiarazione di cui sopra. Il principio di robustezza non proviene dal mondo dell'hardware, ma dall'architettura di Internet, in particolare RFC 1958 . Afferma:

3.9 Sii severo quando invii e tollerante quando ricevi. Le implementazioni devono seguire le specifiche proprio quando si inviano alla rete e tollerare input errati dalla rete. In caso di dubbio, scartare silenziosamente l'input difettoso, senza restituire un messaggio di errore, a meno che ciò non sia richiesto dalle specifiche.

Questo è, chiaramente parlando, semplicemente sbagliato dall'inizio alla fine. È difficile concepire una nozione più errata di gestione degli errori rispetto a "scartare silenziosamente input errati senza restituire un messaggio di errore", per i motivi indicati in questo post.

Vedi anche l'articolo IETF Le conseguenze dannose del principio di robustezza per ulteriori approfondimenti su questo punto.

Mai, mai, mai scegliere questa seconda opzione se non si dispone di risorse equivalenti a team di ricerca di Google per buttare a progetto, perché è quello che ci vuole per elaborare un programma per computer che fa qualcosa di simile a un lavoro decente in quel particolare dominio del problema. (E anche allora, i suggerimenti di Google sembrano usciti dal campo sinistro per circa la metà del tempo.) Se provi a farlo, quello che finirai è un enorme mal di testa in cui il tuo programma cercherà spesso di interpretare input errato come X, quando il mittente intendeva davvero Y.

Questo è negativo per due motivi. Quello ovvio è perché allora hai dei dati cattivi nel tuo sistema. Il meno ovvio è che in molti casi, né tu né il mittente capirete che qualcosa è andato storto fino a molto più tardi lungo la strada quando qualcosa ti è saltato in faccia, e poi improvvisamente hai un grosso e costoso pasticcio da risolvere e nessuna idea cosa è andato storto perché l'effetto evidente è così lontano dalla causa principale.

Questo è il motivo per cui esiste il principio Fail Fast; salva tutte le persone coinvolte nel mal di testa applicandolo alle tue API.


7
Mentre sono d'accordo con il principio di ciò che stai dicendo, penso che ti sbagli WRT l'intento del principio di robustezza. Non ho mai visto che intendesse significare "accettare dati errati", solo "non essere eccessivamente complicato riguardo ai dati positivi". Ad esempio, se l'input è un file CSV, il principio di robustezza non sarebbe un argomento valido per tentare di analizzare le date in un formato imprevisto, ma sosterrebbe un argomento secondo cui inferire l'ordine delle colonne da una riga di intestazione sarebbe una buona idea .
Morgen

9
@Morgen: il principio di robustezza è stato usato per suggerire che i browser dovrebbero accettare HTML piuttosto sciatto, e ha portato a siti Web distribuiti molto più sciatti di quanto sarebbero stati se i browser avessero richiesto un HTML adeguato. Gran parte del problema lì, tuttavia, era l'uso di un formato comune per i contenuti generati dall'uomo e generati dalla macchina, al contrario dell'uso di formati separati modificabili dall'uomo e analizzabili dalla macchina insieme alle utility per la conversione tra di loro.
supercat

9
@supercat: nondimeno - o proprio per questo - HTML e WWW hanno avuto un enorme successo ;-)
Doc Brown

11
@DocBrown: Molte cose davvero orribili sono diventate standard semplicemente perché erano il primo approccio che era disponibile quando qualcuno con molto peso aveva bisogno di adottare qualcosa che soddisfacesse determinati criteri minimi, e quando hanno guadagnato trazione era troppo tardi per selezionare qualcosa di meglio.
supercat

5
@supercat Exactly. JavaScript viene subito in mente, ad esempio ...
Mason Wheeler

13

In generale, il codice dovrebbe essere costruito per sostenere almeno i seguenti vincoli quando possibile:

  1. Quando viene fornito un input corretto, produce un output corretto.

  2. Quando viene fornito un input valido (che può essere o non essere corretto), produce un output valido (allo stesso modo).

  3. Quando viene fornito un input non valido, elaborarlo senza effetti collaterali oltre a quelli causati dall'input normale o quelli definiti come segnalazione di un errore.

In molte situazioni, i programmi passano essenzialmente attraverso vari blocchi di dati senza preoccuparsi in particolare della loro validità. Se tali blocchi contengono dati non validi, l'output del programma conterrà probabilmente dati non validi di conseguenza. A meno che un programma non sia specificamente progettato per convalidare tutti i dati e garantire che non produrrà un output non valido anche quando viene fornito un input non valido , i programmi che elaborano il suo output dovrebbero consentire la possibilità di dati non validi al suo interno.

Sebbene sia spesso auspicabile convalidare i dati all'inizio, non è sempre particolarmente pratico. Tra l'altro, se la validità di un blocco di dati dipende dal contenuto di altri blocchi e se la maggior parte dei dati immessi in una sequenza di passaggi verrà filtrata lungo il percorso, limitando la convalida ai dati che lo superano tutte le fasi possono offrire prestazioni molto migliori rispetto al tentativo di convalidare tutto.

Inoltre, anche se si prevede che a un programma vengano forniti solo dati preconvalidati, è spesso utile che mantenga comunque i vincoli di cui sopra in ogni momento pratico. La ripetizione della convalida completa in ogni fase dell'elaborazione costituirebbe spesso un grave calo delle prestazioni, ma la quantità limitata di convalida necessaria per sostenere i vincoli di cui sopra potrebbe essere molto più economica.


Quindi tutto si riduce a decidere se il risultato di una chiamata API è un "input".
mastov,

@mastov: Le risposte a molte domande dipenderanno da come si definiscono "input" e "comportamenti osservabili" / "output". Se lo scopo di un programma è elaborare numeri memorizzati in un file, il suo input potrebbe essere definito come la sequenza di numeri (nel qual caso le cose che non sono numeri non sono possibili input), o come file (nel qual caso qualsiasi cosa che potrebbe apparire in un file sarebbe un possibile input).
supercat

3

Confrontiamo i due scenari e proviamo a giungere a una conclusione.

Scenario 1 La nostra applicazione presuppone che l'API esterna si comporti secondo l'accordo.

Scenario 2 La nostra applicazione presuppone che l'API esterna possa comportarsi in modo errato, quindi aggiungere precauzioni.

In generale, esiste la possibilità che qualsiasi API o software violi gli accordi; potrebbe essere dovuto a un bug o a condizioni impreviste. Anche un'API potrebbe avere problemi nei sistemi interni con risultati imprevisti.

Se il nostro programma è scritto presupponendo che l'API esterna aderisca agli accordi ed eviti di aggiungere precauzioni; chi sarà la parte che affronterà i problemi? Saremo noi, quelli che hanno scritto il codice di integrazione.

Ad esempio, i valori null selezionati. Ad esempio, secondo l'accordo API, la risposta dovrebbe avere valori non nulli; ma se viene improvvisamente violato il nostro programma si tradurrà in NPE.

Quindi, credo che sarà meglio assicurarsi che l'applicazione disponga di un codice aggiuntivo per affrontare scenari imprevisti.


1

È sempre necessario convalidare i dati in arrivo, immessi dall'utente o in altro modo, quindi è necessario disporre di un processo da gestire quando i dati recuperati da questa API esterna non sono validi.

In generale, qualsiasi giuntura in cui si incontrano sistemi extra-organizzativi dovrebbe richiedere autenticazione, autorizzazione (se non definita semplicemente dall'autenticazione) e convalida.


1

In generale, sì, devi sempre proteggerti da input errati, ma a seconda del tipo di API, "guardia" significa cose diverse.

Per un'API esterna a un server, non si desidera creare accidentalmente un comando che si arresti in modo anomalo o comprometta lo stato del server, quindi è necessario proteggerlo.

Per un'API come ad esempio una classe di contenitore (elenco, vettore, ecc.), Generare eccezioni è un risultato perfettamente corretto, compromettere lo stato dell'istanza di classe può essere accettabile in una certa misura (ad esempio un contenitore ordinato dotato di un operatore di confronto difettoso non lo farà essere ordinati), anche l'arresto anomalo dell'applicazione potrebbe essere accettabile, ma compromettere lo stato dell'applicazione, ad esempio scrivere in posizioni di memoria casuali non correlate all'istanza della classe, molto probabilmente no.


0

Per dare un'opinione leggermente diversa: penso che possa essere accettabile lavorare con i dati che ti vengono dati, anche se viola il suo contratto. Questo dipende dall'uso: è qualcosa che DEVE essere una stringa per te, o è qualcosa che stai solo visualizzando / non usi ecc. In quest'ultimo caso, semplicemente accettalo. Ho un'API che necessita solo dell'1% dei dati forniti da un'altra API. Non potrebbe importarmi di meno che tipo di dati siano nel 99%, quindi non lo controllerò mai.

Deve esserci un equilibrio tra "avere errori perché non controllo abbastanza i miei input" e "rifiuto dati validi perché sono troppo severi".


2
"Ho un'API che necessita solo dell'1% dei dati forniti da un'altra API". Questo apre quindi la domanda sul perché l'API si aspetta 100 volte più dati di quanti ne abbia effettivamente bisogno. Se è necessario archiviare dati opachi da trasmettere, non è necessario essere specifici su ciò che sono e non è necessario dichiararli in alcun formato specifico, nel qual caso il chiamante non violerebbe il contratto .
Voo

1
@Voo - Il mio sospetto è che stiano chiamando alcune API esterne (come "ottieni dettagli meteo per la città X") e quindi selezionando i dati di cui hanno bisogno ("temperatura corrente") e ignorando il resto dei dati restituiti ("pioggia "," vento "," temperatura prevista "," vento gelido ", ecc ...)
Stobor

@ChristianSauer - Penso che tu non sia così lontano da quello che è il consenso più ampio - l'1% dei dati che usi ha senso verificare, ma il 99% che non hai non deve necessariamente essere controllato. Devi solo controllare le cose che potrebbero far scattare il tuo codice.
Stobor,

0

La mia opinione su questo è sempre, controllare sempre ogni input al mio sistema. Ciò significa che ogni parametro restituito da un'API deve essere verificato, anche se il mio programma non lo utilizza. Tendo anche a verificare la correttezza di tutti i parametri che invio a un'API. Ci sono solo due eccezioni a questa regola, vedi sotto.

Il motivo del test è che se per qualche motivo l'API / input non è corretto il mio programma non può fare affidamento su nulla. Forse il mio programma era collegato a una vecchia versione dell'API che fa qualcosa di diverso da quello in cui credo? Forse il mio programma si è imbattuto in un bug nel programma esterno che non è mai successo prima. O peggio ancora, succede sempre ma a nessuno importa! Forse il programma esterno viene ingannato da un hacker per restituire cose che possono danneggiare il mio programma o il sistema?

Le due eccezioni al test di tutto nel mio mondo sono:

  1. Prestazioni dopo un'attenta misurazione delle prestazioni:

    • mai ottimizzare prima di aver misurato. Il test di tutti i dati immessi / restituiti richiede spesso un tempo molto piccolo rispetto alla chiamata effettiva, quindi rimuoverlo spesso consente di risparmiare poco o nulla. Conserverei comunque il codice di rilevamento degli errori, ma lo commenterei, magari con una macro o semplicemente commentandolo.
  2. Quando non hai idea di cosa fare con un errore

    • ci sono momenti, non spesso, in cui il tuo progetto semplicemente non consente la gestione del tipo di errore che potresti trovare. Forse quello che dovresti fare è registrare un errore, ma non ci sono errori di registrazione nel sistema. È quasi sempre possibile trovare un modo per "ricordare" l'errore che consente almeno a te come sviluppatore di verificarlo in seguito. I contatori di errori sono una buona cosa da avere in un sistema, anche se si sceglie di non avere la registrazione.

Esattamente come controllare attentamente gli input / i valori di ritorno è una domanda importante. Ad esempio, se si dice che l'API restituisce una stringa, verificherei che:

  • il tipo di dati actully è una stringa

  • e quella lunghezza è compresa tra i valori minimo e massimo. Controlla sempre le stringhe per la dimensione massima che il mio programma può aspettarsi di gestire (restituire stringhe troppo grandi è un classico problema di sicurezza nei sistemi in rete).

  • Alcune stringhe dovrebbero essere controllate per caratteri o contenuti "illegali" quando ciò è rilevante. Se il tuo programma potrebbe inviare la stringa per dire un database in un secondo momento, è una buona idea controllare gli attacchi al database (ricerca di SQL injection). Questi test vengono eseguiti meglio ai confini del mio sistema, dove posso individuare da dove proviene l'attacco e posso fallire presto. Fare un test di iniezione SQL completo potrebbe essere difficile quando le stringhe vengono successivamente combinate, quindi quel test dovrebbe essere fatto prima di chiamare il database, ma se si possono trovare alcuni problemi in anticipo può essere utile.

Il motivo del test dei parametri che invio all'API è quello di essere sicuro di ottenere un risultato corretto. Ancora una volta, fare questi test prima di chiamare un'API potrebbe sembrare superfluo, ma richiede pochissime prestazioni e potrebbe rilevare errori nel mio programma. Quindi i test sono più preziosi quando si sviluppa un sistema (ma al giorno d'oggi ogni sistema sembra essere in continuo sviluppo). A seconda dei parametri i test possono essere più o meno approfonditi, ma tendo a scoprire che spesso è possibile impostare valori min e max consentiti sulla maggior parte dei parametri che il mio programma potrebbe creare. Forse una stringa dovrebbe sempre avere almeno 2 caratteri e contenere al massimo 2000 caratteri? Il minimo e il massimo dovrebbero essere all'interno di ciò che l'API consente poiché so che il mio programma non utilizzerà mai l'intera gamma di alcuni parametri.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.