<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Testing on My personal blog</title>
        <link>https://www.matteobianchi.eu/tags/testing/</link>
        <description>Recent content in Testing on My personal blog</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en</language>
        <lastBuildDate>Tue, 12 May 2026 09:00:00 +0200</lastBuildDate><atom:link href="https://www.matteobianchi.eu/tags/testing/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Fuzzing: trovare i bug che non hai immaginato</title>
        <link>https://www.matteobianchi.eu/p/fuzzing/</link>
        <pubDate>Tue, 12 May 2026 09:00:00 +0200</pubDate>
        
        <guid>https://www.matteobianchi.eu/p/fuzzing/</guid>
        <description>&lt;img src="https://www.matteobianchi.eu/p/fuzzing/cover.png" alt="Featured image of post Fuzzing: trovare i bug che non hai immaginato" /&gt;&lt;h2 id=&#34;perché-conta&#34;&gt;Perché conta
&lt;/h2&gt;&lt;p&gt;Ogni controllo visto finora — &lt;a class=&#34;link&#34; href=&#34;https://www.matteobianchi.eu/p/sast-analisi-statica/&#34; &gt;SAST&lt;/a&gt;,
&lt;a class=&#34;link&#34; href=&#34;https://www.matteobianchi.eu/p/dast-analisi-dinamica/&#34; &gt;DAST&lt;/a&gt;, &lt;a class=&#34;link&#34; href=&#34;https://www.matteobianchi.eu/p/iast-e-rasp/&#34; &gt;IAST&lt;/a&gt; — cerca
problemi che sappiamo descrivere. I test unitari verificano i casi che &lt;em&gt;immaginiamo&lt;/em&gt;. Ma gli
attaccanti vivono nei casi che &lt;em&gt;non&lt;/em&gt; abbiamo immaginato: l&amp;rsquo;input malformato, la lunghezza assurda, la
sequenza impossibile. Il &lt;strong&gt;fuzzing&lt;/strong&gt; esplora proprio quello spazio, generando input anomali a milioni
e osservando cosa rompe.&lt;/p&gt;
&lt;h2 id=&#34;dallinput-casuale-alla-copertura-guidata&#34;&gt;Dall&amp;rsquo;input casuale alla copertura guidata
&lt;/h2&gt;&lt;p&gt;Il fuzzing ingenuo lancia byte casuali e spera in un crash: inefficiente, perché quasi tutti gli
input vengono rifiutati subito dal parsing. Il salto di qualità è il &lt;strong&gt;coverage-guided fuzzing&lt;/strong&gt;: il
fuzzer osserva &lt;em&gt;quali rami di codice&lt;/em&gt; ogni input attiva e tiene gli input che esplorano codice nuovo,
mutandoli per andare più in profondità.&lt;/p&gt;
&lt;pre class=&#34;mermaid&#34;&gt;
  flowchart LR
    SEED[Corpus iniziale&amp;lt;br/&amp;gt;input validi] --&amp;gt; MUT[Muta l&amp;#39;input&amp;lt;br/&amp;gt;bit flip, splice]
    MUT --&amp;gt; RUN[Esegue la funzione&amp;lt;br/&amp;gt;sotto test]
    RUN --&amp;gt; COV{Copre codice&amp;lt;br/&amp;gt;nuovo?}
    COV --&amp;gt;|sì| KEEP[Aggiungi al corpus&amp;lt;br/&amp;gt;→ base per altre mutazioni]
    COV --&amp;gt;|no| DROP[Scarta]
    RUN --&amp;gt;|crash / hang| BUG[Salva l&amp;#39;input:&amp;lt;br/&amp;gt;bug riproducibile]
    KEEP --&amp;gt; MUT
    style BUG fill:#fde2e4,stroke:#e63946
&lt;/pre&gt;

&lt;p&gt;Guidato dalla copertura, il fuzzer &amp;ldquo;impara&amp;rdquo; a superare i controlli di validazione e a penetrare in
profondità nel codice, trovando in ore ciò che input casuali non troverebbero in anni.&lt;/p&gt;
&lt;h2 id=&#34;cosa-trova&#34;&gt;Cosa trova
&lt;/h2&gt;&lt;p&gt;Il fuzzing eccelle sul codice che &lt;em&gt;interpreta input non fidati&lt;/em&gt;: parser, decoder, deserializzatori,
protocolli di rete, elaborazione di file. Scopre:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Crash&lt;/strong&gt;: dereferenze null, buffer overflow (in C/C++), eccezioni non gestite.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Corruzioni di memoria&lt;/strong&gt;: potenti vettori di exploit, rilevate se si combina il fuzzing con i
&lt;em&gt;sanitizer&lt;/em&gt; (ASan, UBSan) che rendono visibile una corruzione altrimenti silenziosa.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hang e consumo di risorse&lt;/strong&gt;: input che mandano l&amp;rsquo;app in loop o esauriscono la memoria (DoS).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;il-fuzz-target&#34;&gt;Il fuzz target
&lt;/h2&gt;&lt;p&gt;Il cuore pratico è scrivere una piccola funzione che riceve i byte del fuzzer e li passa al codice da
testare:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;LLVMFuzzerTestOneInput&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;uint8_t&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;size_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// il fuzzer fornisce &amp;#39;data&amp;#39;; noi lo passiamo al parser sotto test
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;nf&#34;&gt;parse_config&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;   &lt;span class=&#34;c1&#34;&gt;// ← se crasha, abbiamo un bug riproducibile
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;&lt;/span&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;La riga evidenziata è tutto: dai l&amp;rsquo;input non fidato alla funzione critica e lasci che il fuzzer trovi
l&amp;rsquo;input che la rompe. Lo stesso pattern esiste per i linguaggi gestiti (Go &lt;code&gt;testing.F&lt;/code&gt;, Jazzer per la
JVM, Atheris per Python).&lt;/p&gt;
&lt;h2 id=&#34;continuo-non-una-tantum&#34;&gt;Continuo, non una tantum
&lt;/h2&gt;&lt;p&gt;Il fuzzing dà di più col tempo: più gira, più in profondità arriva. Per questo in DevSecOps è
&lt;strong&gt;continuo&lt;/strong&gt; — gira in background su un corpus che cresce, non in un singolo passaggio di pipeline che
deve finire in cinque minuti. Progetti come OSS-Fuzz dimostrano il modello: fuzzing 24/7, con i bug
che aprono automaticamente ticket. Nella CI si esegue un fuzzing &lt;em&gt;breve&lt;/em&gt; di regressione sul corpus
noto (per non reintrodurre crash già risolti) e si lascia il fuzzing profondo all&amp;rsquo;infrastruttura
dedicata.&lt;/p&gt;


&lt;details&gt;
&lt;summary&gt;&lt;strong&gt;Domanda:&lt;/strong&gt; il fuzzing sembra utile solo per codice in C/C++ che fa parsing di basso livello; ha senso per una tipica app web in un linguaggio gestito?&lt;/summary&gt;
&lt;p&gt;Ha senso, ma cambia cosa trova e dove conviene puntarlo. È vero che il raccolto più ricco del fuzzing —
corruzioni di memoria sfruttabili — vive in C/C++, dove un buffer overflow diventa un exploit; in un
linguaggio gestito la memoria è protetta dal runtime, quindi quella classe sparisce. Ma non sparisce il
resto: eccezioni non gestite che diventano DoS o errori 500 informativi, loop e allocazioni che esauriscono
le risorse (una &#34;zip bomb&#34; o un input che fa esplodere un parser in tempo quadratico), bug logici nei
deserializzatori, differenze di parsing tra due librerie che portano a confusione di tipo o a bypass di
validazione. E soprattutto conta &lt;em&gt;dove&lt;/em&gt; lo punti: non sulla logica di business, ma su ogni punto in
cui l&#39;app ingoia input non fidato e lo struttura — l&#39;endpoint che accetta JSON o XML, il decoder di
immagini, il parser di un formato proprietario, il layer che gestisce upload di file. Lì il fuzzing
coverage-guided trova casi limite che nessun test scritto a mano copre, perché nessuno pensa a un JSON
annidato diecimila livelli o a un campo numerico con un valore che fa overflow in una conversione. In più
le dipendenze native sotto a un&#39;app gestita (una libreria di compressione, un codec) sono spesso C/C++
sotto mentite spoglie, e lì il raccolto classico torna. La regola pratica: se un componente parsa qualcosa
che arriva dall&#39;esterno, è un candidato al fuzzing, qualunque sia il linguaggio.&lt;/p&gt;
&lt;/details&gt;


&lt;h2 id=&#34;conclusione&#34;&gt;Conclusione
&lt;/h2&gt;&lt;p&gt;Il fuzzing esplora lo spazio degli input che non abbiamo immaginato; guidato dalla copertura, impara a
penetrare in profondità e trova crash e corruzioni riproducibili, specie nei parser di input non
fidato. È continuo per natura. Con questo chiudiamo i controlli sul codice e sul comportamento. Ma il
software che spediamo non è solo codice: è un &lt;em&gt;artefatto&lt;/em&gt; costruito da una catena di strumenti, e di
quella catena dobbiamo poterci fidare. Comincia la parte sulla supply chain, con l&amp;rsquo;inventario di ciò
che spediamo: la SBOM, prossimo capitolo.&lt;/p&gt;
</description>
        </item>
        <item>
        <title>DAST: testare l&#39;applicazione viva</title>
        <link>https://www.matteobianchi.eu/p/dast-analisi-dinamica/</link>
        <pubDate>Tue, 28 Apr 2026 09:00:00 +0200</pubDate>
        
        <guid>https://www.matteobianchi.eu/p/dast-analisi-dinamica/</guid>
        <description>&lt;img src="https://www.matteobianchi.eu/p/dast-analisi-dinamica/cover.png" alt="Featured image of post DAST: testare l&#39;applicazione viva" /&gt;&lt;h2 id=&#34;perché-conta&#34;&gt;Perché conta
&lt;/h2&gt;&lt;p&gt;Il &lt;a class=&#34;link&#34; href=&#34;https://www.matteobianchi.eu/p/sast-analisi-statica/&#34; &gt;SAST&lt;/a&gt; e la &lt;a class=&#34;link&#34; href=&#34;https://www.matteobianchi.eu/p/sca-e-dipendenze/&#34; &gt;SCA&lt;/a&gt;
guardano il codice fermo. Ma un&amp;rsquo;applicazione in esecuzione è più della somma del suo sorgente: ha una
configurazione, un web server, header HTTP, sessioni, un ambiente. Il &lt;strong&gt;DAST (Dynamic Application
Security Testing)&lt;/strong&gt; la testa &lt;em&gt;viva&lt;/em&gt;, attaccandola dall&amp;rsquo;esterno come farebbe un avversario, senza
vedere il codice. Trova ciò che esiste solo a runtime.&lt;/p&gt;
&lt;h2 id=&#34;black-box-la-prospettiva-dellattaccante&#34;&gt;Black box: la prospettiva dell&amp;rsquo;attaccante
&lt;/h2&gt;&lt;p&gt;Il DAST non sa nulla del codice: manda richieste, osserva le risposte, deduce.&lt;/p&gt;
&lt;pre class=&#34;mermaid&#34;&gt;
  flowchart LR
    Z[Scanner DAST] --&amp;gt;|crawl: scopre&amp;lt;br/&amp;gt;URL e form| APP[App in esecuzione&amp;lt;br/&amp;gt;staging]
    Z --&amp;gt;|payload malevoli:&amp;lt;br/&amp;gt;&amp;#39;, &amp;lt; script &amp;gt;, ../| APP
    APP --&amp;gt;|risposte,&amp;lt;br/&amp;gt;codici, errori| Z
    Z --&amp;gt; R[Report:&amp;lt;br/&amp;gt;XSS, injection,&amp;lt;br/&amp;gt;header mancanti,&amp;lt;br/&amp;gt;config errata]
    style R fill:#fde2e4,stroke:#e63946
&lt;/pre&gt;

&lt;p&gt;Prima &lt;em&gt;esplora&lt;/em&gt; (crawl) per scoprire URL, parametri e form; poi &lt;em&gt;attacca&lt;/em&gt; iniettando payload e
osservando se l&amp;rsquo;app reagisce in modo rivelatore. Trova:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Configurazioni errate&lt;/strong&gt;: header di sicurezza assenti, cookie senza flag, pagine di errore
troppo verbose, metodi HTTP pericolosi abilitati.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vulnerabilità confermate a runtime&lt;/strong&gt;: una XSS che si attiva davvero, un&amp;rsquo;injection che restituisce
dati, un redirect aperto.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Problemi di sessione e autenticazione&lt;/strong&gt; osservabili dall&amp;rsquo;esterno.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;il-vantaggio-pochi-falsi-positivi&#34;&gt;Il vantaggio: pochi falsi positivi
&lt;/h2&gt;&lt;p&gt;Dove il SAST dice &amp;ldquo;questo &lt;em&gt;potrebbe&lt;/em&gt; essere sfruttabile&amp;rdquo;, il DAST spesso &lt;em&gt;dimostra&lt;/em&gt; lo sfruttamento:
ha inviato il payload e ha visto la risposta. Un finding DAST confermato è quasi sempre reale, perché
riproduce l&amp;rsquo;attacco. Questo lo rende prezioso come controprova dei sospetti del SAST.&lt;/p&gt;
&lt;h2 id=&#34;i-limiti-da-conoscere&#34;&gt;I limiti, da conoscere
&lt;/h2&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Limiti strutturali del DAST:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - Copertura parziale: testa solo i percorsi che riesce a RAGGIUNGERE
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - Lento: una scansione completa può durare ore
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - Non localizza: dice &amp;#34;c&amp;#39;è una XSS qui&amp;#34;, non &amp;#34;riga 42 del file X&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  - Richiede un ambiente in esecuzione, simile a produzione
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;La copertura è il limite più sottile: se lo scanner non riesce a navigare un flusso complesso (un
wizard multi-step, un&amp;rsquo;area dietro login), non lo testa. Per questo si fornisce al DAST
l&amp;rsquo;autenticazione e, idealmente, la mappa degli endpoint (una definizione OpenAPI), così attacca anche
le API, non solo le pagine che riesce a cliccare.&lt;/p&gt;
&lt;h2 id=&#34;nel-flusso-di-lavoro&#34;&gt;Nel flusso di lavoro
&lt;/h2&gt;&lt;p&gt;La lentezza impone una collocazione diversa dal SAST:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scansione &amp;ldquo;baseline&amp;rdquo; veloce&lt;/strong&gt; su ogni deploy in staging: pochi minuti, solo i controlli passivi e
rapidi, come gate leggero.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scansione completa&lt;/strong&gt; notturna o settimanale, fuori dal percorso critico della pipeline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mai in produzione senza cautela&lt;/strong&gt;: il DAST invia payload reali; girarlo su produzione può creare
dati spazzatura o, peggio, innescare azioni. Si usa uno staging fedele.&lt;/li&gt;
&lt;/ul&gt;


&lt;details&gt;
&lt;summary&gt;&lt;strong&gt;Domanda:&lt;/strong&gt; se il DAST trova vulnerabilità confermate e con pochi falsi positivi, non è semplicemente migliore del SAST? Perché tenere entrambi?&lt;/summary&gt;
&lt;p&gt;&#34;Migliore&#34; dipende da cosa misuri, e sulle due metriche che contano di più i due si invertono. Il DAST
vince su &lt;em&gt;precisione&lt;/em&gt; (i suoi finding sono spesso dimostrati) e su &lt;em&gt;realismo&lt;/em&gt; (vede l&#39;app
come un attaccante, con la sua configurazione e il suo ambiente). Ma perde su &lt;em&gt;copertura&lt;/em&gt; e
&lt;em&gt;tempismo&lt;/em&gt;, che per un difetto grave contano quanto la precisione. Copertura: il DAST testa solo i
percorsi che riesce a raggiungere navigando; un ramo di codice che si attiva solo con un input raro non
verrà mai esercitato, mentre il SAST lo legge comunque. Tempismo: il DAST richiede un&#39;app in esecuzione in
staging, quindi arriva a codice già scritto e integrato, mentre il SAST dà feedback sulla pull request,
riga per riga, quando correggere costa un commento invece di un ciclo di rilascio. E c&#39;è una classe di
difetti che il DAST non localizza: ti dice &#34;c&#39;è una XSS raggiungendo questo URL&#34;, non &#34;nasce da questa
funzione&#34; — per il fix serve comunque risalire al codice. Il modello mentale giusto non è una gara ma una
pipeline di reti progressivamente più fini e più tardive: SAST e SCA ampi e precoci sul codice, DAST
realistico e confermante sull&#39;app viva, poi i controlli di runtime in produzione. Scartarne uno non ti dà
più precisione, ti dà un buco in un punto preciso della timeline.&lt;/p&gt;
&lt;/details&gt;


&lt;h2 id=&#34;conclusione&#34;&gt;Conclusione
&lt;/h2&gt;&lt;p&gt;Il DAST attacca l&amp;rsquo;applicazione viva dall&amp;rsquo;esterno: trova configurazioni errate e difetti di runtime
che il codice fermo nasconde, con pochi falsi positivi ma copertura parziale e tempi lunghi. SAST e
DAST insieme coprono codice e comportamento. Esiste però un terzo punto di vista, ibrido: strumentare
l&amp;rsquo;app &lt;em&gt;dall&amp;rsquo;interno&lt;/em&gt; mentre viene testata, per unire visibilità sul codice e realismo del runtime.
Sono IAST e RASP, prossimo capitolo.&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
