Skip to main content

Ανάλυση επιθετικού κώδικα — Πρακτικό εργαστήριο

Σειρά σας. Χτίζετε ένα αποθετήριο Flask σκόπιμα ευάλωτο, το διαβάζετε, το περνάτε από τα Semgrep, Bandit και Gitleaks, παραδίδετε πέντε ή έξι ευρήματα, διορθώνετε, επανασαρώνετε. Η επίδειξη έδειξε πώς να διαβάζετε μια έξοδο. Εδώ, μετράει το παραδοτέο.

Διάρκεια : 2 έως 3 ώρες.

Παραδοτέο : ένας φάκελος ~/analyse-code/flask-audit/ με την πηγή, τις ακατέργαστες εξόδους, ένα rapport.md με έξι ευρήματα, και μία διόρθωση επαληθευμένη με δεύτερη σάρωση.

Αυστηρό πλαίσιο

Ο μοναδικός κώδικας που ελέγχετε είναι αυτός που μόλις δημιουργήσατε σε αυτόν τον φάκελο, ή ένα αποθετήριο lab παρεχόμενο από τον εκπαιδευτή. Κανένα αποθετήριο GitHub αγνώστου, κανένα clone δημόσιου exploit, καμία εφαρμογή τρίτου online. Δεν ανεβάζετε αυτό το lab σε δημόσιο αποθετήριο: περιέχει μοτίβα μυστικών, ακόμα και ψεύτικων, και επικίνδυνες διαδρομές.


Βήμα 1 — Προετοιμασία του εργαστηρίου (15 λεπτά)​

mkdir -p ~/analyse-code/flask-audit/{sorties,correctifs}
cd ~/analyse-code/flask-audit

Δημιουργήστε τέσσερα αρχεία. Αντιγράψτε τα ως έχουν: τα αναγνωριστικά εργαλείων της πλέγματος αυτοαξιολόγησης βασίζονται σε αυτές τις γραμμές.

config.py :

# Τοπικό εργαστήριο — να μην αναπτυχθεί σε παραγωγή, να μην ανεβεί.
AWS_ACCESS_KEY_ID = "AKIAIOSFODNN7EXAMPLE"
AWS_SECRET_ACCESS_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
SLACK_WEBHOOK = "https://hooks.slack.com/services/T00000000/B00000000/exemplecours"
DB_PASSWORD = "SuperSecret123!"

utils.py :

import hashlib
import pickle

def hash_mot_de_passe(mot: str) -> str:
return hashlib.md5(mot.encode("utf-8")).hexdigest()

def charger_session(blob: bytes):
return pickle.loads(blob)

app.py :

import os
import sqlite3
from flask import Flask, request, render_template_string

import config
from utils import hash_mot_de_passe, charger_session

app = Flask(__name__)

@app.route("/user")
def user():
user_id = request.args.get("id", "1")
conn = sqlite3.connect("app.db")
cur = conn.cursor()
cur.execute("SELECT * FROM users WHERE id = %s" % (user_id,))
return str(cur.fetchone())

@app.route("/ping")
def ping():
host = request.args.get("host", "127.0.0.1")
os.system("ping -c 1 %s" % host)
return "pong"

@app.route("/hello")
def hello():
name = request.args.get("name", "monde")
return render_template_string("<h1>Bonjour " + name + "</h1>")

@app.route("/session", methods=["POST"])
def session_charge():
return str(charger_session(request.data))

@app.route("/login", methods=["POST"])
def login():
password = request.form.get("password", "")
attendu = hash_mot_de_passe(config.DB_PASSWORD)
fourni = hash_mot_de_passe(password)
return "ok" if fourni == attendu else "ko"

requirements.txt :

Flask==3.0.3

Εγκαθιστάτε το Flask μόνο αν θέλετε να τρέξετε την εφαρμογή τοπικά μετά τη διόρθωση. Για τον έλεγχο SAST, οι αναλυτές αρκούν.

Στο rapport.md, γράψτε από τώρα την επικεφαλίδα. Χωρίς αυτήν, το υπόλοιπο δεν έχει πλαίσιο.

# Στατική επιθεώρηση — flask-audit
- Ημερομηνία :
- Πεδίο εφαρμογής : αρχεία `app.py`, `utils.py`, `config.py` (τοπικό lab)
- Εργαλεία : Bandit, Semgrep (`p/python`, `p/flask`, `p/secrets`), Gitleaks `--no-git`
- Απόφαση εκτέλεσης : κανένα payload δεν στάλθηκε, κανένα `flask run` πριν το τέλος των ευρημάτων

Βήμα 2 — Στατική ανάγνωση με το χέρι (20 λεπτά)​

Πριν από τα εργαλεία. Ανοίξτε τα τρία αρχεία Python και συμπληρώστε, στο sorties/lecture-manuelle.md, αυτόν τον πίνακα.

ΑρχείοΓραμμήPrimitive ή σήμαΟικογένεια
config.pyμυστικό κωδικοποιημένο εκ των προτέρων
utils.pyMD5 / pickle
app.pySQLi / εντολή / XSS / ροή

Στόχος: έξι γραμμές τουλάχιστον, μία ανά απαιτούμενη οικογένεια (SQLi, command injection, pickle, MD5, μυστικό, XSS). Αν δεν έχετε τις έξι πριν από το Bandit, ξαναδιαβάστε: είναι όλες ορατές χωρίς εργαλείο.

Αναζητήστε επίσης ό,τι τα εργαλεία ίσως δεν εντοπίσουν: το webhook Slack είναι ένα URL κωδικοποιημένο εκ των προτέρων (σήμα του μαθήματος), όχι απλώς ένα «μυστικό AWS».

rg -n "eval|exec|system|pickle|md5|AKIA|authorized_keys|render_template_string|execute" .

Καταχωρήστε την έξοδο στο sorties/rg.txt. Είναι το δίχτυ ασφαλείας σας, όχι η αναφορά σας.


Βήμα 3 — Εκτέλεση των τριών αναλυτών (20 λεπτά)​

cd ~/analyse-code/flask-audit

bandit -r . -f txt -o sorties/bandit.txt
bandit -r . -f json -o sorties/bandit.json

semgrep --config p/python --config p/flask --config p/secrets \
--json --output sorties/semgrep.json .
semgrep --config p/python --config p/flask --config p/secrets \
--text --output sorties/semgrep.txt .

gitleaks detect --source . --no-git -v --report-path sorties/gitleaks.json \
| tee sorties/gitleaks.txt

Αν το Semgrep αρνηθεί μια ρύθμιση, τεκμηριώστε το εναλλακτικό σχέδιο στην αναφορά (--config auto ή τοπικοί κανόνες) και συνεχίστε. Ένα εργαλείο που λείπει αντικαθίσταται με rg επιπλέον της ανάγνωσης· δεν αντικαθίσταται με την εκτέλεση των διαδρομών.

Πρέπει να μπορείτε να δείξετε, για κάθε εργαλείο, ένα μη κενό αρχείο στο sorties/. Αυτό είναι το πρώτο κριτήριο αποδοχής.


Βήμα 4 — Διασταύρωση, όχι αντιγραφή (25 λεπτά)​

Ανοίξτε τις τρεις εξόδους. Χτίστε το sorties/matrice.md :

| Εσωτερικό ID | Γραμμή | Bandit | Semgrep | Gitleaks | Χειροκίνητη ανάγνωση | Ετυμηγορία |
| --- | --- | --- | --- | --- | --- | --- |
| A-01 | | | | | | διατήρηση / θόρυβος / παράδειγμα |

Κανόνες ετυμηγορίας:

  • Διατήρηση : η γραμμή κάνει αυτό που υποστηρίζει το εργαλείο (εισερχόμενο δεδομένο μέσα σε execute, os.system, pickle.loads, συνενωμένο HTML, hash κωδικού πρόσβασης σε MD5, μυστικό στην πηγή).
  • Παράδειγμα : το AKIAIOSFODNN7EXAMPLE και το μυστικό AWS της τεκμηρίωσης. Το διατηρείτε ούτως ή άλλως ως εύρημα μοτίβου (το αποθετήριο διδάσκει την αποθήκευση κλειδιών στο config.py), χαρακτηρίζοντάς το «κλειδί τεκμηρίωσης».
  • Θόρυβος : μια ειδοποίηση σε μια σταθερά χωρίς εισερχόμενη ροή, ή ένα ακριβές διπλότυπο. Ένα μόνο εύρημα ανά αδυναμία, ακόμα και αν μιλούν τρία εργαλεία.

Παραδίδετε πέντε ή έξι διατηρηθέντα ευρήματα, όχι τριάντα screenshots. Οι έξι οικογένειες του θέματος πρέπει να εμφανίζονται. Αν το Bandit ξεχάσει το XSS, το Semgrep ή η χειροκίνητη ανάγνωσή σας το φέρνει.

Ένα εύρημα, μία αδυναμία

Η login καλεί δύο φορές την hash_mot_de_passe. Είναι ένα εύρημα MD5 (η primitive στο utils.py), όχι δύο. Η διαδρομή /session και η charger_session είναι ένα εύρημα pickle. Ο πελάτης διορθώνει ένα σημείο, όχι μία ειδοποίηση ανά εργαλείο.


Βήμα 5 — Συγγραφή των έξι ευρημάτων (40 λεπτά)​

Κάθε εύρημα του rapport.md ακολουθεί τον ίδιο σκελετό. Αντιγράψτε το πρότυπο έξι φορές (A-01 έως A-06). Τα εσωτερικά μπλοκ κώδικα παραμένουν με τρεις ανάστροφες παύλες· το εξωτερικό πρότυπο δίνεται εδώ με τέσσερις για να μεταγλωττίζεται η σελίδα — στο δικό σας rapport.md (απλό Markdown), τρεις αρκούν παντού.

## A-0X — `<οικογένεια>` : `<τίτλος σε μία γραμμή>`

- **Αρχείο / γραμμή** :
- **Εργαλεία** : (π.χ. Bandit B608, Semgrep tainted-sql-string)
- **Προϋπόθεση** : ποιος μπορεί να προσεγγίσει τι, χωρίς να εκτελεστεί κανένα exploit
- **Απόδειξη** (απόσπασμα) :

```python
# επικολλήστε ΜΟΝΟ την ελαττωματική γραμμή, όχι όλο το αρχείο
```

- **Επίπτωση** : μία έως τρεις προτάσεις, προνόμιο και συνέπεια
- **Αποκατάσταση** : η ενέργεια, όχι «να ασφαλιστεί ο κώδικας»
- **Επανεπαλήθευση** : εντολή και κριτήριο (το ID του εργαλείου εξαφανίζεται, ή η γραμμή δεν
υπάρχει πλέον)

Τι πρέπει να αποδεικνύει κάθε οικογένεια​

Δεν στέλνετε κανένα payload HTTP. Η απόδειξη είναι η πηγή.

Προτεινόμενο IDΟικογένειαΕλάχιστη απόδειξηΕπίπτωση προς διατύπωσηΑναμενόμενη αποκατάσταση
A-01SQLicur.execute με format και user_id από το request.argsSQL αίτημα ελεγχόμενο από τον πελάτηΠαραμετρικό αίτημα ? / binding
A-02Εντολήos.system + hostΕντολή συστήματος ελεγχόμενηsubprocess σε λίστα, χωρίς shell, επικυρωμένος host (allowlist)
A-03XSSrender_template_string + συνένωση του nameHTML αντανακλαστικό, πιθανό script στον browserΠρότυπο Jinja με αυτόματο escaping, χωρίς συνένωση
A-04Picklepickle.loads πάνω στο request.dataΕκτέλεση κώδικα κατά τη φόρτωσηjson + σχήμα, ή διαγραφή της διαδρομής
A-05MD5hashlib.md5 πάνω σε κωδικό πρόσβασηςΣπάσιμο του hash, όχι ασφαλής αποθήκευση κωδικούargon2 / bcrypt, ποτέ MD5 ή SHA-1 για μυστικό
A-06Μυστικόκλειδιά στο config.pyΔιαρροή από τον διαμοιρασμό του φακέλουΜεταβλητές περιβάλλοντος, αρχείο εκτός αποθετηρίου, αφαίρεση κλειδιών παραδείγματος

Παράδειγμα συνταγμένο για το A-04 — τα άλλα πέντε είναι δικά σας, στο ίδιο επίπεδο ακρίβειας.

## A-04 — Αποσειριοποίηση : `pickle.loads` σε `POST /session`

- **Αρχείο / γραμμή** : `utils.py` (`charger_session`) καλείται από το `app.py` (`session_charge`)
- **Εργαλεία** : Bandit B301, Semgrep `python.lang.security.deserialization.pickle.avoid-pickle`
- **Προϋπόθεση** : προσέγγιση του `POST /session` (ανώνυμη διαδρομή σε αυτό το lab)
- **Απόδειξη** :

```python
def charger_session(blob: bytes):
return pickle.loads(blob)
```

- **Επίπτωση** : το σώμα του αιτήματος αποσειριοποιείται. Ένα αντικείμενο pickle δεν είναι
δεδομένο: είναι πρόγραμμα. Η διεργασία Flask εκτελείται με τα δικαιώματα του λογαριασμού που
την εκκινεί.
- **Αποκατάσταση** : διαγραφή της `charger_session` και της διαδρομής, ή αποδοχή μόνο
περιορισμένου JSON (`json.loads` + αναμενόμενα κλειδιά).
- **Επανεπαλήθευση** : `rg -n pickle utils.py app.py` χωρίς αντιστοίχιση· το Bandit δεν αναφέρει
πλέον το B301.

Βήμα 6 — Μία διόρθωση, έπειτα η επανασάρωση (30 λεπτά)​

Επιλέξτε τουλάχιστον δύο ευρήματα ανάμεσα στα A-01, A-02, A-04 (τα τρία που είναι εκτελέσεις κώδικα). Τα μυστικά (A-06) μετράνε επίσης: η εξαγωγή προς το περιβάλλον είναι μια διόρθωση ορατή από το Gitleaks.

Δουλέψτε σε αντίγραφα, όχι πατώντας πάνω χωρίς δίχτυ:

cp app.py correctifs/app.py.avant
cp utils.py correctifs/utils.py.avant
cp config.py correctifs/config.py.avant

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

# A-01 — app.py
cur.execute("SELECT * FROM users WHERE id = ?", (user_id,))

# A-02 — app.py
import subprocess
subprocess.run(["ping", "-c", "1", "--", host], check=False)
# και, πριν από την κλήση: απόρριψη κάθε host που δεν βρίσκεται σε μια allowlist IP/ονομάτων

# A-03 — app.py
from flask import render_template
# πρότυπο templates/hello.html : <h1>Bonjour {{ name }}</h1>
return render_template("hello.html", name=name)

# A-04 — utils.py + app.py
import json
def charger_session(blob: bytes):
return json.loads(blob.decode("utf-8"))

# A-05 — utils.py
from hashlib import sha256 # ανεπαρκές για πραγματικό κωδικό πρόσβασης
# αναμενόμενο στην αναφορά : bcrypt ή argon2, όχι άλλο MD
# παράδειγμα κατεύθυνσης :
# import bcrypt
# return bcrypt.hashpw(mot.encode(), bcrypt.gensalt()).decode()

# A-06 — config.py
import os
AWS_ACCESS_KEY_ID = os.environ["AWS_ACCESS_KEY_ID"]
AWS_SECRET_ACCESS_KEY = os.environ["AWS_SECRET_ACCESS_KEY"]
DB_PASSWORD = os.environ["DB_PASSWORD"]

Για το A-03, το πρότυπο Jinja κάνει escape από προεπιλογή: μια παρεμβολή {{ name }} δεν είναι πλέον συνένωση HTML. Δημιουργήστε το templates/hello.html αν διορθώσετε το XSS.

Επανεκτελέστε τις ίδιες εντολές, προς διαφορετικά αρχεία:

bandit -r . -f txt -o sorties/bandit-apres.txt
semgrep --config p/python --config p/flask --config p/secrets \
--text --output sorties/semgrep-apres.txt .
gitleaks detect --source . --no-git -v --report-path sorties/gitleaks-apres.json \
| tee sorties/gitleaks-apres.txt

Στο rapport.md, μία ενότητα Επανεπαλήθευση :

## Επανεπαλήθευση

| ID | Πριν (ID εργαλείου ή γραμμή) | Μετά | Κατάσταση |
| --- | --- | --- | --- |
| A-01 | B608 / execute % | απόν από bandit-apres.txt | διορθωμένο / μερικό / αποτυχία |
| A-02 | | | |
| A-04 | | | |
| A-06 | AKIA στο gitleaks.json | | |

«Μερικό» είναι έντιμο: το subprocess.run σε λίστα κλείνει την injection μέσω shell, αλλά ένας μη επικυρωμένος host παραμένει πρόβλημα. Το σημειώνετε. Μια διόρθωση που φιμώνει το Bandit προσθέτοντας # nosec χωρίς να αλλάξει τη ροή είναι αποτυχία: αναφέρετέ το αν το δοκιμάσατε, έπειτα αφαιρέστε το.

Να μη «διορθώνετε» φιμώνοντας το εργαλείο

Τα # nosec, nosemgrep και οι εξαιρέσεις Gitleaks γίνονται δεκτά μόνο για ένα τεκμηριωμένο ψευδώς θετικό στο εύρημα. Οι έξι οικογένειες αυτού του εργαστηρίου δεν είναι ψευδώς θετικά.


Βήμα 7 — Επαληθεύσιμο παραδοτέο​

Η τελική δομή, όπως την ξανανοίγει ένας διορθωτής:

~/analyse-code/flask-audit/
app.py
utils.py
config.py
requirements.txt
templates/hello.html # αν διορθώθηκε το A-03
sorties/
lecture-manuelle.md
rg.txt
matrice.md
bandit.txt
bandit.json
semgrep.txt
semgrep.json
gitleaks.txt
gitleaks.json
bandit-apres.txt
semgrep-apres.txt
gitleaks-apres.txt
gitleaks-apres.json
correctifs/
app.py.avant
utils.py.avant
config.py.avant
rapport.md

Το rapport.md περιέχει, με αυτή τη σειρά:

  1. Την επικεφαλίδα του βήματος 1 (πεδίο εφαρμογής, εργαλεία, απόφαση να μην εκτελεστεί payload).
  2. Την πλέγμα διασταύρωσης (ή μια σαφή παραπομπή στο sorties/matrice.md).
  3. Πέντε ή έξι ευρήματα με το πρότυπο του βήματος 5, με όλες τις οικογένειες παρούσες.
  4. Την ενότητα Επανεπαλήθευση με τουλάχιστον δύο γραμμές στην κατάσταση διορθωμένο ή μερικό, με αποδείξεις στα sorties/*-apres.*.
  5. Μία πρόταση για ό,τι δεν κάνατε: κανένα python app.py πριν το τέλος των ευρημάτων, καμία δημόσια ανάρτηση, κανένα payload.

Πλέγμα αυτοαξιολόγησης​

  • Το πεδίο εφαρμογής χωράει σε τρία αρχεία Python δημιουργημένα για το εργαστήριο (ή lab εκπαιδευτή με ονομασία).
  • Μια χειροκίνητη ανάγνωση προηγείται των εργαλείων, με έξι εντοπισμένες οικογένειες.
  • Τα sorties/bandit.txt, sorties/semgrep.txt και sorties/gitleaks.txt υπάρχουν και δεν είναι κενά.
  • Η πλέγμα διασταυρώνει τα εργαλεία: όχι ένα εύρημα ανά ειδοποίηση, ένα εύρημα ανά αδυναμία.
  • Έξι ευρήματα: SQLi, εντολή, XSS, pickle, MD5, μυστικό — καθένα με απόδειξη πηγής, επίπτωση, αποκατάσταση, κριτήριο επανασάρωσης.
  • Τα κλειδιά AKIAIOSFODNN7EXAMPLE χαρακτηρίζονται ως παράδειγμα, όχι ως πραγματική διαρροή AWS.
  • Τουλάχιστον δύο διορθώσεις εφαρμοσμένες, τα αντίγραφα correctifs/*.avant διατηρημένα.
  • Μια δεύτερη σάρωση δείχνει την εξαφάνιση (ή τη μερική κατάσταση) των σχετικών IDs.
  • Κανένα payload δεν στάλθηκε σε τρίτο μηχάνημα. Κανένα δημόσιο exploit δεν εκτελέστηκε.
  • Ο φάκελος δεν βρίσκεται σε δημόσιο GitHub.

Όλα σημειωμένα; Το παραδοτέο είναι αποδεκτό.


Τι μπλοκάρει γενικά​

  • Το Semgrep αποτυγχάνει εκτός σύνδεσης. Χρησιμοποιήστε τις ρυθμίσεις που έχετε ήδη κατεβάσει, ή Bandit + rg δηλώνοντάς το. Μην επινοήσετε μια έξοδο.
  • Το Gitleaks δεν βλέπει τίποτα. Ελέγξτε το --no-git και ότι το config.py βρίσκεται όντως στον τρέχοντα φάκελο. Ένα gitleaks protect σε άδειο αποθετήριο δεν σαρώνει τα μη παρακολουθούμενα αρχεία.
  • Το Bandit είναι σιωπηλό για το XSS. Αυτό είναι αναμενόμενο. Το XSS είναι το εύρημα της ανάγνωσής σας / του Semgrep, όχι μια τρύπα στη δουλειά σας.
  • Πειρασμός να κάνετε curl τις διαδρομές. Το εργαστήριο είναι μια επιθεώρηση. Μια επιβεβαίωση DAST βγαίνει εκτός θέματος και, σε ένα κακά εκκινημένο Flask, βγαίνει μερικές φορές εκτός 127.0.0.1.
  • Αναφορά = επικόλληση των JSON. Ο διορθωτής διαβάζει το rapport.md. Τα JSON είναι παραρτήματα.

Τι κερδίζετε από αυτό το εργαστήριο​

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