Πώς να αποτρέψετε επιθέσεις τύπου SQL Injection | SQLite3

  • Reading time:6 mins read

Τι είναι η επιθέση SQL Injection?

Η επίθεση SQL injection ή αλλιώς SQLI είναι μία τεχνική που μπορεί να χρησιμοποιήσει κάποιος κακόβουλος χρήστης με σκοπό να αποκτήσει πρόσβαση στην βάση δεδομένων μας.

Για να καταλάβουμε καλύτερα πώς μπορεί να γίνει κάτι τέτοιο θα το δούμε στην πράξη με ένα παράδειγμα.
Καλό θα ήταν πριν ξεκινήσετε να έχετε μία βασική γνώση της γλώσσας Python και της SQL. Για την SQL μπορείτε να ρίξετε μία ματιά σε ένα παλιότερο μάθημά μου.

Δημιουργία της Βάσης Δεδομένων

Ξεκινάμε δημιουργούντας μία βάση δεδομένων με την sqlite3 και την ονομάζουμε users.db. Έπειτα φτιάχνουμε και έναν πίνακα με το όνομα users.

import sqlite3

conn = sqlite3.connect('users.db')
c = conn.cursor()

c.execute("""
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY,
        username TEXT,
        password TEXT
    )
;""")

conn.commit()
conn.close() 

Χρησιμοποιούμε την μέθοδο .connect() για να συνδεθούμε στην βάση δεδομένων μας. Εάν δεν υπάρχει η συγκεκριμένη βάση δεδομένων τότε η μέθοδος .connect() θα την δημιουργήσει.
Έπειτα δημιουργούμε το αντικείμενο cursor() το οποίο θα χρησιμοποιούμε για να εκτελούμε τις εντολές στην βάση δεδομένων μας.
Στην συνέχεια φτιάχνουμε τον πίνακα users ο οποίος περιέχει το username και το password από τους χρήστες και μία στήλη με το id τους.
Για να εκτελέσουμε μία εντολή που περιέχει κώδικα SQL πρέπει να χρησιμοποιήσουμε τον κέρσορα (cursor) που δημιουργήσαμε πριν και να καλέσουμε την μέθοδο .execute() μέσα στην οποία θα γράψουμε το SQL query που θέλουμε να τρέξουμε.
Τέλος κάνουμε commit() τις αλλαγές μας στην βάση και κλείνουμε την σύνδεση με το .close().

Εισαγωγή δεδομένων στον πίνακα

Αφού φτιάξαμε τον πίνακα τώρα θα προσθέσουμε και μερικές εγγραφές.

c.execute("""
    INSERT INTO users (username, password) VALUES
    ('chris', 'dg3achris'),
    ('john22', 'john33ff3'),
    ('maria_mal', 'fg4mards'),
    ('eleni2002', 'elenpass')
;""") 

Καλούμε την μέθοδο .execute() πριν από την μέθοδο .commit() και δημιουργούμε 4 νέες εγγραφές στον πίνακα.

Λειτουργία Προγραμματος

Αρχικά θα φτιάξουμε δύο μεταβλητές οι οποίες θα δέχονται το username και το password από τον χρήστη. Έπειτα θα καλούμε μία εντολή SELECT η οποία θα επιστρέφει τα στοιχεία από τον χρήστη εάν το username και το password αντιστοιχούν σε κάποιον χρήστη από την βάση δεδομένων μας.

name = input('Your Username: ')
code = input('Your Password: ')

c.execute(f"""
SELECT * FROM users
WHERE username = '{name}'
AND password = '{code}'
;""") 

Για να μπορέσουμε να δούμε το αποτέλεσμα της εντολής SELECT θα πρέπει να καλεσουμε την μέθοδο .fetchall() η οποία επιστρέφει όλα τα αποτελέσματα της SELECT μέσα σε μία λίστα. Αν θέλουμε κάθε αποτέλεσμα σε διαφορετική σειρά μπορούμε να καλέσουμε μία for loop.

rows = c.fetchall()
for row in rows:
    print(row) 

Πάμε τώρα να δούμε τι θα επιστρέφει το πρόγραμμά μας ανάλογα με το τι θα εισάγει ο χρήστης στις μεταβλητές name και code. Αφού δημιουργήσουμε τον πίνακα δεν χρειαζόμαστε πλέον τις δύο εντολές CREATE και INSERT οπότε μπορούμε να τις σβήσουμε ή να πάμε σε νέο αρχείο. Αυτή τη στιγμή ο κώδικάς μας είναι ο παρακάτω. 

import sqlite3

conn = sqlite3.connect('users.db')
c = conn.cursor()

name = input('Your Username: ')
code = input('Your Password: ')

c.execute(f"""
SELECT * FROM users
WHERE username = '{name}'
AND password = '{code}'
;""")

rows = c.fetchall()
for row in rows:
    print(row)

conn.close() 

Όταν εκτελέσουμε το πρόγραμμα μας θα μας βγάλει στο terminal την φράση Your Username: και ο χρήστης θα πρέπει να εισάγει το όνομά του, αφού πατήσει enter εμφανίζεται και η φράση Your Password: όπου ο χρήστης πρέπει να γράψει τον κωδικό του και να πατήσει ξανά enter.
Έαν βάλουμε λάθος όνομα ή λάθος κωδικό παρατηρούμε ότι δεν επιστρέφει τίποτα. Αν όμως το όνομα και ο κωδικός είναι σωστά τότε επιστρέφει την εγγραφή με αυτά τα στοιχεία.
Στον πίνακά μας έχουμε προσθέσει μόνο όνομα και κωδικό οπότε θα επιστρέψει αυτά συν το id.
Για τον πρώτο χρήστη δηλαδή θα επιστρέψει (1, ‘chris’, ‘dg3achris’).

Επίθεση SQL Injection

Ως εδώ όλα καλά, το πρόγραμμα δουλεύει όπως θα έπρεπε. Τι γίνεται όμως όταν ένας κακόβουλος χρήστης προσπαθήσει να αποκτήσει πρόσβαση σε πράγματα που δεν θα έπρεπε να έχει πρόσβαση;
Πάμε να δούμε πώς μπορεί να κάνει κάτι τέτοιο. Γράφουμε ένα τυχαίο όνομα ας πούμε admin αλλά για κωδικό γράφουμε το εξής: ‘ OR ‘1’=’1
Γράφωντας αυτό στο πεδίο του κωδικού το πρόγραμμα επιστρέφει όλο τον πίνακα users 😧.

Your Username: admin
Your Password: ' OR '1'='1

(1, 'chris', 'dg3achris')
(2, 'john22', 'john33ff3')
(3, 'maria_mal', 'fg4mards')
(4, 'eleni2002', 'elenpass') 

Γιατί συνέβει αυτό; Πάμε να δούμε το αρχικό select statement.

SELECT * FROM users WHERE username = '{name}' AND password = '{code}'; 

Το οποίο αφού πήρε τα στοιχεία που εισήγαγε ο χρήστης έγινε το εξής.

SELECT * FROM users WHERE username = 'admin' AND password = '' OR '1'='1'; 

Εδώ βλέπουμε πώς ο χρήστης μπόρεσε να αλλάξει την εντολή select προσθέτοντας στο τέλος ‘1’=’1′ το οποίο είναι πάντα αληθές. Οπότε η εντολή select θα επιστρέψει όλες τις εγγραφες και ο χρήστης θα αποκτήσει πρόσβαση σε όλη την βάση μας. Αυτή λοιπόν είναι η επίθεση SQL Injection, όταν κάποιος κακόβουλος χρήστης προσπαθεί να αλλάξει μία εντολή της SQL με σκοπό να αποκτήσει πρόσβαση σε πληροφορίες που κανονικά δεν θα έπρεπε να έχει πρόσβαση.

Αποτροπή επίθεσης SQL Injection

Πώς μπορούμε όμως να αποφύγουμε αυτές τις επιθέσεις; Αυτό που πρέπει αρχικά να κάνουμε είναι να μην χρησιμοποιούμε f-strings για να εισάγουμε τα στοιχεία στο SQL query μας. Καθώς μία f-string μας δίνει την δυνατότητα να προσθέσουμε εκτελέσιμο κώδικα μέσα στο κείμενο της.
Οπότε αντί για f-strings μπορούμε να χρησιμοποιήσουμε μία τεχνική που λέγεται “παραμετροποιμένα ερωτήματα”. Αυτή η τεχνική ξεχωρίζει το SQL query από τις τιμές που εισήγαγε ο χρήστης. Οι τιμές περνιούνται σαν παράμετροι και όχι σαν εκτελέσιμος κώδικας.

Πάμε να δούμε πώς μπορούμε να το κάνουμε αυτό στην πράξη.

c.execute("""
SELECT * FROM users
WHERE username = ?
AND password = ?
;""", (name, code)) 

Αφαιρούμε το f-string και στην θέση των παραμέτρων προσθέτουμε ένα ?. Το ερωτηματικό δρα σαν ένα placeholder. Αφού τελειώσει το query μας προσθέτουμε μέσα σε παρενθέση τις τιμές που θέλουμε να μπουν στην θέση των ερωτηματικών. Με αυτόν τον τρόπο οι χρήστες δεν μπορούν να μεταβάλουν το query μας γιατί οι τιμές περνιούνται σαν παράμετροι μέσα στο query μας. Οπότε αν ο χρήστης γράψει OR ‘1’=’1 τότε το query μας θα είναι κάπως έτσι.

SELECT * FROM users WHERE username = "admin" AND password = "' OR '1'='1"; 

Σε αυτή την περίπτωση το query μας θα ελέγξει εάν ο κωδικός του χρήστη admin είναι ίσος με την τιμή: ’ OR ‘ 1’=’1.

Ανακεφαλαιώνοντας

Οπότε για να αποφύγουμε τις επιθέσεις τύπου SQL Injection θα πρέπει να αποφεύγουμε να χρησιμοποιηούμε f-strings για να ορίζουμε τις παραμέτρους για του query μας, αντιθέτως θα πρέπει να χρησιμοποιηούμε “παραμετροποιμένα ερωτήματα” ώστε οι χρήστες να μην μπορούν να αλλάξουν την μορφή από το query μας.

Αν βρήκες ενδιαφέρον αυτό το μάθημα μπορείς να μας κεράσεις έναν καφέ ή να το μοιραστείς με τους φίλους στα  social media.

Buy Me a Coffee at ko-fi.com
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments