sql >> Database teknologi >  >> RDS >> Database

Forebyggelse af SQL-injektionsangreb med Python

Hvert par år rangerer Open Web Application Security Project (OWASP) de mest kritiske sikkerhedsrisici for webapplikationer. Siden den første rapport har injektionsrisici altid været på toppen. Blandt alle injektionstyper SQL-injektion er en af ​​de mest almindelige angrebsvektorer, og uden tvivl den farligste. Da Python er et af de mest populære programmeringssprog i verden, er det vigtigt at vide, hvordan man beskytter mod Python SQL-injektion.

I dette selvstudie skal du lære:

  • Hvilken Python SQL-injektion er, og hvordan man forhindrer det
  • Sådan skriver du forespørgsler med både bogstaver og identifikatorer som parametre
  • Sådan udføres forespørgsler sikkert i en database

Denne vejledning er velegnet til brugere af alle databasemotorer . Eksemplerne her bruger PostgreSQL, men resultaterne kan gengives i andre databasestyringssystemer (såsom SQLite, MySQL, Microsoft SQL Server, Oracle, og så videre).

Gratis bonus: 5 Thoughts On Python Mastery, et gratis kursus for Python-udviklere, der viser dig køreplanen og den tankegang, du skal bruge for at tage dine Python-færdigheder til næste niveau.


Forstå Python SQL Injection

SQL Injection-angreb er en så almindelig sikkerhedssårbarhed, at den legendariske xkcd webcomic dedikerede en tegneserie til det:

Generering og eksekvering af SQL-forespørgsler er en almindelig opgave. Men virksomheder rundt om i verden laver ofte frygtelige fejl, når det kommer til at komponere SQL-sætninger. Mens ORM-laget normalt sammensætter SQL-forespørgsler, skal du nogle gange skrive dine egne.

Når du bruger Python til at udføre disse forespørgsler direkte i en database, er der en chance for, at du kan lave fejl, der kan kompromittere dit system. I dette selvstudie lærer du, hvordan du med succes implementerer funktioner, der sammensætter dynamiske SQL-forespørgsler uden sætter dit system i fare for Python SQL-injektion.



Opsætning af en database

For at komme i gang skal du opsætte en ny PostgreSQL-database og udfylde den med data. Igennem selvstudiet vil du bruge denne database til at se på egen hånd, hvordan Python SQL-injektion fungerer.


Oprettelse af en database

Åbn først din shell og opret en ny PostgreSQL-database, der ejes af brugeren postgres :

$ createdb -O postgres psycopgtest

Her brugte du kommandolinjemuligheden -O for at indstille ejeren af ​​databasen til brugeren postgres . Du har også angivet navnet på databasen, som er psycopgtest .

Bemærk: postgres er en særlig bruger , som du normalt reserverer til administrative opgaver, men til denne øvelse er det fint at bruge postgres . I et rigtigt system bør du dog oprette en separat bruger til at være ejer af databasen.

Din nye database er klar til brug! Du kan oprette forbindelse til den ved hjælp af psql :

$ psql -U postgres -d psycopgtest
psql (11.2, server 10.5)
Type "help" for help.

Du er nu forbundet til databasen psycopgtest som brugeren postgres . Denne bruger er også databaseejeren, så du har læsetilladelser på hver tabel i databasen.



Oprettelse af en tabel med data

Dernæst skal du oprette en tabel med nogle brugeroplysninger og tilføje data til den:

psycopgtest=# CREATE TABLE users (
    username varchar(30),
    admin boolean
);
CREATE TABLE

psycopgtest=# INSERT INTO users
    (username, admin)
VALUES
    ('ran', true),
    ('haki', false);
INSERT 0 2

psycopgtest=# SELECT * FROM users;
 username | admin
----------+-------
 ran      | t
 haki     | f
(2 rows)

Tabellen har to kolonner:username og admin . admin kolonne angiver, om en bruger har administrative rettigheder eller ej. Dit mål er at målrette mod admin og prøv at misbruge det.



Opsætning af et virtuelt Python-miljø

Nu hvor du har en database, er det tid til at konfigurere dit Python-miljø. For trin-for-trin instruktioner om, hvordan du gør dette, tjek Python Virtual Environments:A Primer.

Opret dit virtuelle miljø i en ny mappe:

(~/src) $ mkdir psycopgtest
(~/src) $ cd psycopgtest
(~/src/psycopgtest) $ python3 -m venv venv

Efter du har kørt denne kommando, en ny mappe kaldet venv vil blive oprettet. Denne mappe gemmer alle de pakker, du installerer i det virtuelle miljø.



Opretter forbindelse til databasen

For at oprette forbindelse til en database i Python skal du bruge en databaseadapter . De fleste databaseadaptere følger version 2.0 af Python Database API Specification PEP 249. Hver større databasemotor har en førende adapter:

Database Adapter
PostgreSQL Psychopg
SQLite sqlite3
Oracle cx_oracle
MySql MySQLdb

For at oprette forbindelse til en PostgreSQL-database skal du installere Psycopg, som er den mest populære adapter til PostgreSQL i Python. Django ORM bruger det som standard, og det understøttes også af SQLAlchemy.

I din terminal skal du aktivere det virtuelle miljø og bruge pip for at installere psycopg :

(~/src/psycopgtest) $ source venv/bin/activate
(~/src/psycopgtest) $ python -m pip install psycopg2>=2.8.0
Collecting psycopg2
  Using cached https://....
  psycopg2-2.8.2.tar.gz
Installing collected packages: psycopg2
  Running setup.py install for psycopg2 ... done
Successfully installed psycopg2-2.8.2

Nu er du klar til at oprette en forbindelse til din database. Her er starten på dit Python-script:

import psycopg2

connection = psycopg2.connect(
    host="localhost",
    database="psycopgtest",
    user="postgres",
    password=None,
)
connection.set_session(autocommit=True)

Du brugte psycopg2.connect() for at skabe forbindelsen. Denne funktion accepterer følgende argumenter:

  • host er IP-adressen eller DNS'en på den server, hvor din database er placeret. I dette tilfælde er værten din lokale maskine eller localhost .

  • database er navnet på den database, der skal oprettes forbindelse til. Du vil oprette forbindelse til den database, du oprettede tidligere, psycopgtest .

  • user er en bruger med tilladelser til databasen. I dette tilfælde vil du oprette forbindelse til databasen som ejer, så du videregiver brugeren postgres .

  • password er adgangskoden til den du har angivet i user . I de fleste udviklingsmiljøer kan brugere oprette forbindelse til den lokale database uden adgangskode.

Efter at have oprettet forbindelsen, konfigurerede du sessionen med autocommit=True . Aktiverer autocommit betyder, at du ikke behøver at administrere transaktioner manuelt ved at udstede en commit eller rollback . Dette er standardadfærden i de fleste ORM'er. Du bruger også denne adfærd her, så du kan fokusere på at komponere SQL-forespørgsler i stedet for at administrere transaktioner.

Bemærk: Django-brugere kan få forekomsten af ​​forbindelsen brugt af ORM fra django.db.connection :

from django.db import connection


Udførelse af en forespørgsel

Nu hvor du har forbindelse til databasen, er du klar til at udføre en forespørgsel:

>>>
>>> with connection.cursor() as cursor:
...     cursor.execute('SELECT COUNT(*) FROM users')
...     result = cursor.fetchone()
... print(result)
(2,)

Du brugte connection objekt for at oprette en cursor . Ligesom en fil i Python, cursor implementeres som kontekstmanager. Når du opretter konteksten, vises en cursor er åbnet, så du kan bruge det til at sende kommandoer til databasen. Når konteksten afsluttes, vises cursor lukker, og du kan ikke længere bruge den.

Bemærk: For at lære mere om kontekstadministratorer, tjek Python Context Managers og "med"-erklæringen.

Mens du var inde i konteksten, brugte du cursor for at udføre en forespørgsel og hente resultaterne. I dette tilfælde udsendte du en forespørgsel for at tælle rækkerne i users bord. For at hente resultatet fra forespørgslen, udførte du cursor.fetchone() og modtog en tupel. Da forespørgslen kun kan returnere ét resultat, brugte du fetchone() . Hvis forespørgslen skulle returnere mere end ét resultat, skal du enten iterere over cursor eller brug en af ​​de andre fetch* metoder.




Brug af forespørgselsparametre i SQL

I det foregående afsnit oprettede du en database, oprettede en forbindelse til den og udførte en forespørgsel. Den forespørgsel, du brugte, var statisk . Med andre ord havde den ingen parametre . Nu begynder du at bruge parametre i dine forespørgsler.

Først skal du implementere en funktion, der kontrollerer, om en bruger er administrator eller ej. is_admin() accepterer et brugernavn og returnerer denne brugers administratorstatus:

# BAD EXAMPLE. DON'T DO THIS!
def is_admin(username: str) -> bool:
    with connection.cursor() as cursor:
        cursor.execute("""
            SELECT
                admin
            FROM
                users
            WHERE
                username = '%s'
        """ % username)
        result = cursor.fetchone()
    admin, = result
    return admin

Denne funktion udfører en forespørgsel for at hente værdien af ​​admin kolonne for et givet brugernavn. Du brugte fetchone() at returnere en tuple med et enkelt resultat. Derefter pakkede du denne tuple ud i variablen admin . For at teste din funktion, tjek nogle brugernavne:

>>>
>>> is_admin('haki')
False
>>> is_admin('ran')
True

Så langt så godt. Funktionen returnerede det forventede resultat for begge brugere. Men hvad med ikke-eksisterende bruger? Tag et kig på denne Python-sporing:

>>>
>>> is_admin('foo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 12, in is_admin
TypeError: cannot unpack non-iterable NoneType object

Når brugeren ikke eksisterer, vises en TypeError er hævet. Dette er fordi .fetchone() returnerer None når der ikke findes nogen resultater, og udpakning af None rejser en TypeError . Det eneste sted, du kan pakke en tuple ud, er, hvor du udfylder admin fra result .

For at håndtere ikke-eksisterende brugere skal du oprette en speciel sag for hvornår result er None :

# BAD EXAMPLE. DON'T DO THIS!
def is_admin(username: str) -> bool:
    with connection.cursor() as cursor:
        cursor.execute("""
            SELECT
                admin
            FROM
                users
            WHERE
                username = '%s'
        """ % username)
        result = cursor.fetchone()

    if result is None:
        # User does not exist
        return False

    admin, = result
    return admin

Her har du tilføjet en speciel sag til håndtering af None . Hvis username ikke eksisterer, så skulle funktionen returnere False . Test igen funktionen på nogle brugere:

>>>
>>> is_admin('haki')
False
>>> is_admin('ran')
True
>>> is_admin('foo')
False

Store! Funktionen kan nu også håndtere ikke-eksisterende brugernavne.



Udnyttelse af forespørgselsparametre med Python SQL Injection

I det forrige eksempel brugte du strenginterpolation til at generere en forespørgsel. Derefter udførte du forespørgslen og sendte den resulterende streng direkte til databasen. Der er dog noget, du måske har overset under denne proces.

Tænk tilbage på username argument, du sendte til is_admin() . Hvad repræsenterer denne variabel præcist? Du kan antage, at username er blot en streng, der repræsenterer en faktisk brugers navn. Som du dog er ved at se, kan en ubuden gæst nemt udnytte denne form for tilsyn og forårsage stor skade ved at udføre Python SQL-injektion.

Prøv at kontrollere, om følgende bruger er administrator eller ej:

>>>
>>> is_admin("'; select true; --")
True

Vent... Hvad skete der lige?

Lad os se på implementeringen igen. Udskriv den faktiske forespørgsel, der udføres i databasen:

>>>
>>> print("select admin from users where username = '%s'" % "'; select true; --")
select admin from users where username = ''; select true; --'

Den resulterende tekst indeholder tre udsagn. For at forstå præcis, hvordan Python SQL-injektion fungerer, skal du inspicere hver del individuelt. Den første erklæring er som følger:

select admin from users where username = '';

Dette er din tilsigtede forespørgsel. Semikolonet (; ) afslutter forespørgslen, så resultatet af denne forespørgsel er ligegyldigt. Dernæst er det andet udsagn:

select true;

Denne udtalelse blev konstrueret af den ubudne gæst. Den er designet til altid at returnere True .

Til sidst ser du denne korte kodebit:

--'

Dette uddrag defuserer alt, der kommer efter det. Den ubudne gæst tilføjede kommentarsymbolet (-- ) for at gøre alt, hvad du måtte have sat efter den sidste pladsholder til en kommentar.

Når du udfører funktionen med dette argument, vil den altid returnere True . Hvis du for eksempel bruger denne funktion på din login-side, kan en ubuden gæst logge ind med brugernavnet '; select true; -- , og de vil få adgang.

Hvis du synes, det er slemt, kan det blive værre! Ubudne gæster med viden om din tabelstruktur kan bruge Python SQL-injektion til at forårsage permanent skade. For eksempel kan den ubudne gæst injicere en opdateringserklæring for at ændre oplysningerne i databasen:

>>>
>>> is_admin('haki')
False
>>> is_admin("'; update users set admin = 'true' where username = 'haki'; select true; --")
True
>>> is_admin('haki')
True

Lad os opdele det igen:

';

Dette uddrag afslutter forespørgslen, ligesom i den forrige indsprøjtning. Det næste udsagn er som følger:

update users set admin = 'true' where username = 'haki';

Denne sektion opdaterer admin til true for brugeren haki .

Endelig er der dette kodestykke:

select true; --

Som i det foregående eksempel returnerer denne brik true og kommenterer alt, hvad der følger efter det.

Hvorfor er det værre? Nå, hvis den ubudne gæst formår at udføre funktionen med dette input, så bruger haki bliver en admin:

psycopgtest=# select * from users;
 username | admin
----------+-------
 ran      | t
 haki     | t
(2 rows)

Den ubudne gæst behøver ikke længere bruge hacket. De kan bare logge ind med brugernavnet haki . (Hvis den ubudne gæst virkelig ønskede at forårsage skade, så kunne de endda udstede en DROP DATABASE kommando.)

Inden du glemmer det, gendan haki tilbage til sin oprindelige tilstand:

psycopgtest=# update users set admin = false where username = 'haki';
UPDATE 1

Så hvorfor sker det? Nå, hvad ved du om username argument? Du ved, at det skal være en streng, der repræsenterer brugernavnet, men du kontrollerer eller håndhæver faktisk ikke denne påstand. Dette kan være farligt! Det er præcis, hvad angribere leder efter, når de forsøger at hacke dit system.


Udarbejdelse af sikre forespørgselsparametre

I det forrige afsnit så du, hvordan en ubuden gæst kan udnytte dit system og få administratortilladelser ved at bruge en omhyggeligt udformet streng. Problemet var, at du tillod, at værdien, der blev sendt fra klienten, blev eksekveret direkte til databasen uden at udføre nogen form for kontrol eller validering. SQL-injektioner er afhængige af denne type sårbarhed.

Hver gang brugerinput bruges i en databaseforespørgsel, er der en mulig sårbarhed for SQL-injektion. Nøglen til at forhindre Python SQL-injektion er at sikre, at værdien bliver brugt, som udvikleren havde til hensigt. I det foregående eksempel havde du til hensigt username skal bruges som en snor. I virkeligheden blev det brugt som en rå SQL-sætning.

For at sikre, at værdier bruges, som de er beregnet til, skal du escape værdien. For f.eks. at forhindre ubudne gæster i at injicere rå SQL i stedet for et strengargument, kan du undslippe anførselstegn:

>>>
>>> # BAD EXAMPLE. DON'T DO THIS!
>>> username = username.replace("'", "''")

Dette er blot ét eksempel. Der er en masse specialtegn og scenarier at tænke på, når du forsøger at forhindre Python SQL-injektion. Heldigt for dig, moderne databaseadaptere, kommer med indbyggede værktøjer til at forhindre Python SQL-injektion ved at bruge forespørgselsparametre . Disse bruges i stedet for almindelig strenginterpolation til at komponere en forespørgsel med parametre.

Bemærk: Forskellige adaptere, databaser og programmeringssprog henviser til forespørgselsparametre med forskellige navne. Almindelige navne inkluderer bindingsvariabler , erstatningsvariabler og erstatningsvariabler .

Nu hvor du har en bedre forståelse af sårbarheden, er du klar til at omskrive funktionen ved hjælp af forespørgselsparametre i stedet for strenginterpolation:

 1def is_admin(username: str) -> bool:
 2    with connection.cursor() as cursor:
 3        cursor.execute("""
 4            SELECT
 5                admin
 6            FROM
 7                users
 8            WHERE
 9                username = %(username)s
10        """, {
11            'username': username
12        })
13        result = cursor.fetchone()
14
15    if result is None:
16        # User does not exist
17        return False
18
19    admin, = result
20    return admin

Her er, hvad der er anderledes i dette eksempel:

  • I linje 9, du brugte en navngivet parameter username for at angive, hvor brugernavnet skal hen. Bemærk, hvordan parameteren username er ikke længere omgivet af enkelte anførselstegn.

  • I linje 11, du har videregivet værdien af ​​username som det andet argument til cursor.execute() . Forbindelsen vil bruge typen og værdien af ​​username når du udfører forespørgslen i databasen.

For at teste denne funktion, prøv nogle gyldige og ugyldige værdier, inklusive den farlige streng fra før:

>>>
>>> is_admin('haki')
False
>>> is_admin('ran')
True
>>> is_admin('foo')
False
>>> is_admin("'; select true; --")
False

Fantastiske! Funktionen returnerede det forventede resultat for alle værdier. Hvad mere er, den farlige streng virker ikke længere. For at forstå hvorfor, kan du inspicere forespørgslen genereret af execute() :

>>>
>>> with connection.cursor() as cursor:
...    cursor.execute("""
...        SELECT
...            admin
...        FROM
...            users
...        WHERE
...            username = %(username)s
...    """, {
...        'username': "'; select true; --"
...    })
...    print(cursor.query.decode('utf-8'))
SELECT
    admin
FROM
    users
WHERE
    username = '''; select true; --'

Forbindelsen behandlede værdien af ​​username som en streng og undslippede alle tegn, der kunne afslutte strengen og introducere Python SQL-injektion.



Beståelse af sikre forespørgselsparametre

Databaseadaptere tilbyder normalt flere måder at sende forespørgselsparametre på. Navngivne pladsholdere er normalt bedst til læsbarhed, men nogle implementeringer kan drage fordel af at bruge andre muligheder.

Lad os tage et hurtigt kig på nogle af de rigtige og forkerte måder at bruge forespørgselsparametre på. Følgende kodeblok viser de typer forespørgsler, du vil undgå:

# BAD EXAMPLES. DON'T DO THIS!
cursor.execute("SELECT admin FROM users WHERE username = '" + username + '");
cursor.execute("SELECT admin FROM users WHERE username = '%s' % username);
cursor.execute("SELECT admin FROM users WHERE username = '{}'".format(username));
cursor.execute(f"SELECT admin FROM users WHERE username = '{username}'");

Hver af disse udsagn sender username fra klienten direkte til databasen uden at udføre nogen form for kontrol eller validering. Denne slags kode er moden til at invitere Python SQL-injektion.

I modsætning hertil burde disse typer forespørgsler være sikre for dig at udføre:

# SAFE EXAMPLES. DO THIS!
cursor.execute("SELECT admin FROM users WHERE username = %s'", (username, ));
cursor.execute("SELECT admin FROM users WHERE username = %(username)s", {'username': username});

I disse udsagn, username videregives som en navngivet parameter. Nu vil databasen bruge den angivne type og værdi af username når du udfører forespørgslen, tilbyder beskyttelse mod Python SQL-injektion.




Brug af SQL-sammensætning

Indtil videre har du brugt parametre for bogstaver. Bogstave er værdier som tal, strenge og datoer. Men hvad nu hvis du har en use case, der kræver, at du komponerer en anden forespørgsel – en hvor parameteren er noget andet, f.eks. et tabel- eller kolonnenavn?

Inspireret af det foregående eksempel, lad os implementere en funktion, der accepterer navnet på en tabel og returnerer antallet af rækker i den tabel:

# BAD EXAMPLE. DON'T DO THIS!
def count_rows(table_name: str) -> int:
    with connection.cursor() as cursor:
        cursor.execute("""
            SELECT
                count(*)
            FROM
                %(table_name)s
        """, {
            'table_name': table_name,
        })
        result = cursor.fetchone()

    rowcount, = result
    return rowcount

Prøv at udføre funktionen på din brugertabel:

>>>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in count_rows
psycopg2.errors.SyntaxError: syntax error at or near "'users'"
LINE 5:                 'users'
                        ^

Kommandoen kunne ikke generere SQL. Som du allerede har set, behandler databaseadapteren variablen som en streng eller en literal. Et tabelnavn er dog ikke en almindelig streng. Det er her SQL-sammensætning kommer ind.

Du ved allerede, at det ikke er sikkert at bruge strenginterpolation til at komponere SQL. Heldigvis tilbyder Psycopg et modul kaldet psycopg.sql for at hjælpe dig med at komponere SQL-forespørgsler sikkert. Lad os omskrive funktionen ved hjælp af psycopg.sql.SQL() :

from psycopg2 import sql

def count_rows(table_name: str) -> int:
    with connection.cursor() as cursor:
        stmt = sql.SQL("""
            SELECT
                count(*)
            FROM
                {table_name}
        """).format(
            table_name = sql.Identifier(table_name),
        )
        cursor.execute(stmt)
        result = cursor.fetchone()

    rowcount, = result
    return rowcount

Der er to forskelle i denne implementering. Først brugte du sql.SQL() at sammensætte forespørgslen. Derefter brugte du sql.Identifier() for at kommentere argumentværdien table_name . (En identifikator er et kolonne- eller tabelnavn.)

Bemærk: Brugere af den populære pakke django-debug-toolbar kan få en fejl i SQL-panelet for forespørgsler sammensat med psycopg.sql.SQL() . En rettelse forventes til udgivelse i version 2.0.

Prøv nu at udføre funktionen på users tabel:

>>>
>>> count_rows('users')
2

Store! Lad os derefter se, hvad der sker, når tabellen ikke eksisterer:

>>>
>>> count_rows('foo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in count_rows
psycopg2.errors.UndefinedTable: relation "foo" does not exist
LINE 5:                 "foo"
                        ^

Funktionen kaster UndefinedTable undtagelse. I de følgende trin vil du bruge denne undtagelse som en indikation af, at din funktion er sikker mod et Python SQL-injektionsangreb.

Bemærk: Undtagelsen UndefinedTable blev tilføjet i psycopg2 version 2.8. Hvis du arbejder med en tidligere version af Psycopg, får du en anden undtagelse.

For at sætte det hele sammen skal du tilføje en mulighed for at tælle rækker i tabellen op til en vis grænse. Denne funktion kan være nyttig til meget store borde. For at implementere dette skal du tilføje en LIMIT klausul til forespørgslen sammen med forespørgselsparametre for grænsens værdi:

from psycopg2 import sql

def count_rows(table_name: str, limit: int) -> int:
    with connection.cursor() as cursor:
        stmt = sql.SQL("""
            SELECT
                COUNT(*)
            FROM (
                SELECT
                    1
                FROM
                    {table_name}
                LIMIT
                    {limit}
            ) AS limit_query
        """).format(
            table_name = sql.Identifier(table_name),
            limit = sql.Literal(limit),
        )
        cursor.execute(stmt)
        result = cursor.fetchone()

    rowcount, = result
    return rowcount

I denne kodeblok annoterede du limit ved hjælp af sql.Literal() . Som i det foregående eksempel, psycopg vil binde alle forespørgselsparametre som bogstaver, når du bruger den simple tilgang. Men når du bruger sql.SQL() , skal du udtrykkeligt annotere hver parameter ved at bruge enten sql.Identifier() eller sql.Literal() .

Bemærk: Desværre omhandler Python API-specifikationen ikke binding af identifikatorer, kun bogstaver. Psycopg er den eneste populære adapter, der tilføjede muligheden for sikkert at komponere SQL med både bogstaver og identifikatorer. Dette faktum gør det endnu vigtigere at være meget opmærksom, når du binder identifikatorer.

Udfør funktionen for at sikre, at den virker:

>>>
>>> count_rows('users', 1)
1
>>> count_rows('users', 10)
2

Nu hvor du ser, at funktionen virker, skal du sørge for, at den også er sikker:

>>>
>>> count_rows("(select 1) as foo; update users set admin = true where name = 'haki'; --", 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 18, in count_rows
psycopg2.errors.UndefinedTable: relation "(select 1) as foo; update users set admin = true where name = '" does not exist
LINE 8:                     "(select 1) as foo; update users set adm...
                            ^

Denne sporing viser, at psycopg escaped værdien, og databasen behandlede den som et tabelnavn. Da en tabel med dette navn ikke eksisterer, er en UndefinedTable undtagelsen blev rejst, og du blev ikke hacket!



Konklusion

Du har implementeret en funktion, der sammensætter dynamisk SQL uden sætter dit system i fare for Python SQL-injektion! Du har brugt både bogstaver og identifikatorer i din forespørgsel uden at kompromittere sikkerheden.

Du har lært:

  • Hvilken Python SQL-injektion er, og hvordan det kan udnyttes
  • Sådan forhindrer man Python SQL-injektion ved hjælp af forespørgselsparametre
  • Sådan komponeres SQL-sætninger sikkert der bruger bogstaver og identifikatorer som parametre

Du er nu i stand til at oprette programmer, der kan modstå angreb udefra. Gå frem og modarbejde hackerne!



  1. PostgreSQL returnerer en funktion med en brugerdefineret datatype

  2. Hvordan vælger jeg effektivt den tidligere ikke-nul værdi?

  3. Hvordan genskaber man en slettet tabel med Django Migrations?

  4. Sådan installeres SQL * PLUS-klient i linux