sql >> Databáze >  >> RDS >> Database

Prevence útoků SQL Injection pomocí Pythonu

Každých několik let hodnotí projekt Open Web Application Security Project (OWASP) nejkritičtější bezpečnostní rizika webových aplikací. Od první zprávy byla rizika vstřikování vždy na vrcholu. Mezi všemi typy vkládání je vkládání SQL je jedním z nejběžnějších vektorů útoku a pravděpodobně nejnebezpečnější. Jelikož je Python jedním z nejpopulárnějších programovacích jazyků na světě, je důležité vědět, jak se chránit před Python SQL injection.

V tomto tutoriálu se naučíte:

  • Co Python SQL injection je a jak tomu zabránit
  • Jak vytvářet dotazy s literály i identifikátory jako parametry
  • Jak bezpečně spouštět dotazy v databázi

Tento výukový program je vhodný pro uživatele všech databázových strojů . Zde uvedené příklady používají PostgreSQL, ale výsledky lze reprodukovat v jiných systémech správy databází (jako je SQLite, MySQL, Microsoft SQL Server, Oracle atd.).

Box zdarma: 5 Thoughts On Python Mastery, bezplatný kurz pro vývojáře Pythonu, který vám ukáže plán a způsob myšlení, které budete potřebovat, abyste své dovednosti v Pythonu posunuli na další úroveň.


Pochopení Python SQL Injection

Útoky SQL Injection jsou tak běžnou bezpečnostní chybou, jako je legendární xkcd webcomic tomu věnoval komiks:

Generování a provádění SQL dotazů je běžným úkolem. Společnosti po celém světě však často dělají hrozné chyby, pokud jde o skládání SQL příkazů. Zatímco vrstva ORM obvykle skládá SQL dotazy, někdy musíte napsat své vlastní.

Když používáte Python ke spouštění těchto dotazů přímo do databáze, existuje šance, že uděláte chyby, které by mohly ohrozit váš systém. V tomto tutoriálu se naučíte, jak úspěšně implementovat funkce, které skládají dynamické SQL dotazy bez vystavuje váš systém riziku pro Python SQL injection.



Nastavení databáze

Chcete-li začít, nastavíte novou databázi PostgreSQL a naplníte ji daty. V průběhu kurzu budete tuto databázi používat k tomu, abyste byli z první ruky svědky toho, jak funguje Python SQL injection.


Vytvoření databáze

Nejprve otevřete svůj shell a vytvořte novou PostgreSQL databázi vlastněnou uživatelem postgres :

$ createdb -O postgres psycopgtest

Zde jste použili volbu příkazového řádku -O pro nastavení vlastníka databáze na uživatele postgres . Zadali jste také název databáze, což je psycopgtest .

Poznámka: postgres je zvláštní uživatel , které byste normálně vyhradili pro administrativní úkoly, ale pro tento tutoriál je dobré použít postgres . Ve skutečném systému byste však měli vytvořit samostatného uživatele, který bude vlastníkem databáze.

Vaše nová databáze je připravena k použití! Můžete se k němu připojit pomocí psql :

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

Nyní jste připojeni k databázi psycopgtest jako uživatel postgres . Tento uživatel je také vlastníkem databáze, takže budete mít oprávnění ke čtení u každé tabulky v databázi.



Vytvoření tabulky s daty

Dále musíte vytvořit tabulku s některými informacemi o uživateli a přidat do ní data:

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)

Tabulka má dva sloupce:username a admin . admin udává, zda má uživatel oprávnění správce. Vaším cílem je zacílit na admin pole a pokusit se jej zneužít.



Nastavení virtuálního prostředí Python

Nyní, když máte databázi, je čas nastavit prostředí Pythonu. Podrobné pokyny, jak to udělat, najdete v Python Virtual Environments:A Primer.

Vytvořte své virtuální prostředí v novém adresáři:

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

Po spuštění tohoto příkazu se objeví nový adresář s názvem venv bude vytvořen. Tento adresář bude ukládat všechny balíčky, které nainstalujete ve virtuálním prostředí.



Připojování k databázi

Chcete-li se připojit k databázi v Pythonu, potřebujete adaptér databáze . Většina databázových adaptérů se řídí verzí 2.0 specifikace Python Database API Specification PEP 249. Každý hlavní databázový stroj má přední adaptér:

Databáze Adaptér
PostgreSQL Psycopg
SQLite sqlite3
Oracle cx_oracle
MySql MySQLdb

Chcete-li se připojit k databázi PostgreSQL, budete muset nainstalovat Psycopg, což je nejoblíbenější adaptér pro PostgreSQL v Pythonu. Django ORM jej používá ve výchozím nastavení a je také podporován SQLAlchemy.

Ve svém terminálu aktivujte virtuální prostředí a použijte pip k instalaci 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

Nyní jste připraveni vytvořit připojení k vaší databázi. Zde je začátek vašeho skriptu Python:

import psycopg2

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

Použili jste psycopg2.connect() k vytvoření spojení. Tato funkce přijímá následující argumenty:

  • host je IP adresa nebo DNS serveru, kde se nachází vaše databáze. V tomto případě je hostitelem váš místní počítač nebo localhost .

  • database je název databáze, ke které se chcete připojit. Chcete se připojit k databázi, kterou jste vytvořili dříve, psycopgtest .

  • user je uživatel s oprávněním k databázi. V tomto případě se chcete připojit k databázi jako vlastník, takže předáte uživateli postgres .

  • password je heslo pro kohokoli, koho jste zadali v user . Ve většině vývojových prostředí se uživatelé mohou připojit k místní databázi bez hesla.

Po nastavení připojení jste relaci nakonfigurovali pomocí autocommit=True . Aktivace autocommit znamená, že nebudete muset ručně spravovat transakce vydáváním commit nebo rollback . Toto je výchozí chování ve většině ORM. Toto chování využijete i zde, abyste se místo správy transakcí mohli soustředit na skládání SQL dotazů.

Poznámka: Uživatelé Django mohou získat instanci připojení používaného ORM z django.db.connection :

from django.db import connection


Provedení dotazu

Nyní, když máte připojení k databázi, jste připraveni provést dotaz:

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

Použili jste connection objekt k vytvoření cursor . Stejně jako soubor v Pythonu, cursor je implementován jako kontextový manažer. Když vytvoříte kontext, cursor se otevře, abyste mohli odesílat příkazy do databáze. Když kontext opustíte, cursor se zavře a už jej nemůžete používat.

Poznámka: Chcete-li se dozvědět více o kontextových manažerech, podívejte se na Python Context Managers a na příkaz „with“.

V kontextu jste použili cursor k provedení dotazu a načtení výsledků. V tomto případě jste zadali dotaz na počítání řádků v users stůl. Chcete-li načíst výsledek z dotazu, spustili jste cursor.fetchone() a obdržel n-tici. Protože dotaz může vrátit pouze jeden výsledek, použili jste fetchone() . Pokud by dotaz měl vrátit více než jeden výsledek, pak byste museli buď iterovat přes cursor nebo použijte jeden z dalších fetch* metody.




Použití parametrů dotazu v SQL

V předchozí části jste vytvořili databázi, navázali připojení k ní a provedli dotaz. Použitý dotaz byl statický . Jinými slovy, neměl žádné parametry . Nyní začnete ve svých dotazech používat parametry.

Nejprve implementujete funkci, která kontroluje, zda je uživatel správcem. is_admin() přijme uživatelské jméno a vrátí tomuto uživateli status správce:

# 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

Tato funkce provede dotaz k načtení hodnoty admin sloupec pro dané uživatelské jméno. Použili jste fetchone() vrátit n-tici s jediným výsledkem. Poté jste tuto n-tici rozbalili do proměnné admin . Chcete-li otestovat svou funkci, zkontrolujte některá uživatelská jména:

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

Zatím je vše dobré. Funkce vrátila očekávaný výsledek pro oba uživatele. Ale co neexistující uživatel? Podívejte se na tento traceback Pythonu:

>>>
>>> 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

Když uživatel neexistuje, zobrazí se TypeError je zvednutý. Důvodem je .fetchone() vrátí None když nejsou nalezeny žádné výsledky, a rozbalení None vyvolá TypeError . Jediné místo, kde můžete rozbalit n-tici, je místo, kde zadáte admin z result .

Chcete-li pracovat s neexistujícími uživateli, vytvořte speciální případ pro result je 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

Zde jste přidali speciální případ pro zpracování None . Pokud username neexistuje, pak by funkce měla vrátit False . Ještě jednou otestujte funkci na některých uživatelích:

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

Skvělý! Funkce nyní zvládne i neexistující uživatelská jména.



Využití parametrů dotazu pomocí Python SQL Injection

V předchozím příkladu jste ke generování dotazu použili řetězcovou interpolaci. Poté jste provedli dotaz a odeslali výsledný řetězec přímo do databáze. Je tu však něco, co jste během tohoto procesu mohli přehlédnout.

Vzpomeňte si na username argument, který jste předali is_admin() . Co přesně tato proměnná představuje? Můžete předpokládat, že username je pouze řetězec, který představuje skutečné uživatelské jméno. Jak však uvidíte, vetřelec může tento druh dohledu snadno zneužít a způsobit velké škody provedením Python SQL injection.

Zkuste zkontrolovat, zda je následující uživatel správcem nebo ne:

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

Počkejte... Co se právě stalo?

Podívejme se ještě jednou na implementaci. Vytiskněte skutečný dotaz, který se provádí v databázi:

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

Výsledný text obsahuje tři výroky. Abyste přesně porozuměli tomu, jak Python SQL injection funguje, musíte zkontrolovat každou část jednotlivě. První příkaz je následující:

select admin from users where username = '';

Toto je váš zamýšlený dotaz. Středník (; ) ukončí dotaz, takže na výsledku tohoto dotazu nezáleží. Další na řadě je druhý příkaz:

select true;

Toto prohlášení vytvořil vetřelec. Je navržen tak, aby vždy vrátil True .

Nakonec vidíte tento krátký kód:

--'

Tento úryvek zneškodňuje vše, co přijde po něm. Vetřelec přidal symbol komentáře (-- ), abyste změnili vše, co jste mohli vložit za poslední zástupný symbol, na komentář.

Když spustíte funkci s tímto argumentem, vždy vrátí True . Pokud například použijete tuto funkci na své přihlašovací stránce, mohl by se narušitel přihlásit pomocí uživatelského jména '; select true; -- a bude jim udělen přístup.

Pokud si myslíte, že je to špatné, může to být ještě horší! Vetřelci se znalostí struktury vaší tabulky mohou pomocí Python SQL injection způsobit trvalé poškození. Vetřelec může například vložit aktualizační příkaz ke změně informací v databázi:

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

Pojďme si to znovu rozebrat:

';

Tento fragment ukončí dotaz, stejně jako v předchozí injekci. Další příkaz je následující:

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

Tato sekce aktualizuje admin true pro uživatele haki .

Nakonec je tu tento fragment kódu:

select true; --

Stejně jako v předchozím příkladu tato část vrací true a okomentuje vše, co za ním následuje.

proč je to horší? Pokud se útočníkovi podaří provést funkci s tímto vstupem, pak uživatel haki stane se správcem:

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

Vetřelec již nemusí používat hack. Mohou se pouze přihlásit pomocí uživatelského jména haki . (Pokud vetřelec opravdu chtěli způsobit škodu, pak mohli dokonce vydat DROP DATABASE příkaz.)

Než zapomenete, obnovte haki zpět do původního stavu:

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

Proč se to děje? Co víte o username argument? Víte, že by to měl být řetězec představující uživatelské jméno, ale ve skutečnosti toto tvrzení nekontrolujete ani nevynucujete. To může být nebezpečné! To je přesně to, co útočníci hledají, když se snaží hacknout váš systém.


Vytváření bezpečných parametrů dotazu

V předchozí části jste viděli, jak může vetřelec zneužít váš systém a získat oprávnění správce pomocí pečlivě vytvořeného řetězce. Problém byl v tom, že jste dovolili, aby se hodnota předaná z klienta spustila přímo do databáze, aniž byste museli provádět jakoukoli kontrolu nebo ověřování. Injekce SQL spoléhají na tento typ zranitelnosti.

Kdykoli je v databázovém dotazu použit vstup uživatele, existuje možná zranitelnost pro SQL injection. Klíčem k zabránění vkládání jazyka Python SQL je zajistit, aby se hodnota používala tak, jak vývojář zamýšlel. V předchozím příkladu jste zamýšleli pro username k použití jako řetězec. Ve skutečnosti byl použit jako nezpracovaný příkaz SQL.

Chcete-li se ujistit, že se hodnoty používají tak, jak jsou zamýšleny, musíte escape hodnota. Chcete-li například zabránit vetřelcům vložit nezpracované SQL místo argumentu řetězce, můžete uvozovky obejít:

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

Toto je jen jeden příklad. Existuje mnoho speciálních postav a scénářů, na které je třeba myslet, když se pokoušíte zabránit vkládání Pythonu SQL. Naštěstí pro vás, moderní databázové adaptéry, přicházejí s vestavěnými nástroji pro zabránění vkládání jazyka Python SQL pomocí parametrů dotazu . Ty se používají místo interpolace prostého řetězce k sestavení dotazu s parametry.

Poznámka: Různé adaptéry, databáze a programovací jazyky odkazují na parametry dotazu pod různými názvy. Mezi běžné názvy patří bind variables , náhradní proměnné a substituční proměnné .

Nyní, když lépe rozumíte zranitelnosti, jste připraveni přepsat funkci pomocí parametrů dotazu namísto interpolace řetězců:

 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

Zde je to, co se v tomto příkladu liší:

  • V řádku 9 použili jste pojmenovaný parametr username k označení, kam má uživatelské jméno směřovat. Všimněte si, jak parametr username již není ohraničeno jednoduchými uvozovkami.

  • V řádku 11 předali jste hodnotu username jako druhý argument pro cursor.execute() . Připojení bude používat typ a hodnotu username při provádění dotazu v databázi.

Chcete-li tuto funkci otestovat, vyzkoušejte některé platné a neplatné hodnoty, včetně nebezpečného řetězce z dříve:

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

Úžasný! Funkce vrátila očekávaný výsledek pro všechny hodnoty. A co víc, nebezpečný řetězec již nefunguje. Abyste pochopili proč, můžete si prohlédnout dotaz vygenerovaný funkcí 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; --'

Připojení zpracovalo hodnotu username jako řetězec a escapoval všechny znaky, které by mohly ukončit řetězec a zavést Python SQL injection.



Předávání parametrů bezpečného dotazu

Databázové adaptéry obvykle nabízejí několik způsobů, jak předat parametry dotazu. Pojmenované zástupné symboly jsou obvykle nejlepší pro čitelnost, ale některým implementacím může prospět použití jiných možností.

Pojďme se rychle podívat na některé správné a nesprávné způsoby použití parametrů dotazu. Následující blok kódu ukazuje typy dotazů, kterým se chcete vyhnout:

# 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}'");

Každý z těchto příkazů předává username z klienta přímo do databáze, bez provádění jakékoli kontroly nebo ověřování. Tento druh kódu je zralý pro pozvání Python SQL injection.

Naproti tomu tyto typy dotazů by pro vás měly být bezpečné:

# 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});

V těchto příkazech username je předán jako pojmenovaný parametr. Nyní bude databáze používat zadaný typ a hodnotu username při provádění dotazu nabízí ochranu před Python SQL injection.




Použití SQL Composition

Dosud jste používali parametry pro literály. Literály jsou hodnoty, jako jsou čísla, řetězce a data. Ale co když máte případ použití, který vyžaduje sestavení jiného dotazu – takového, kde je parametrem něco jiného, ​​například název tabulky nebo sloupce?

Na základě předchozího příkladu implementujme funkci, která přijímá název tabulky a vrací počet řádků v této tabulce:

# 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

Zkuste spustit funkci na vaší tabulce uživatelů:

>>>
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'
                        ^

Příkazu se nepodařilo vygenerovat SQL. Jak jste již viděli, databázový adaptér zachází s proměnnou jako s řetězcem nebo literálem. Název tabulky však není prostý řetězec. Zde přichází na řadu složení SQL.

Už víte, že není bezpečné používat k sestavení SQL interpolaci řetězců. Naštěstí Psycopg poskytuje modul nazvaný psycopg.sql které vám pomohou bezpečně skládat SQL dotazy. Přepišme funkci pomocí 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

V této implementaci jsou dva rozdíly. Nejprve jste použili sql.SQL() sestavit dotaz. Potom jste použili sql.Identifier() k anotaci hodnoty argumentu table_name . (identifikátor je název sloupce nebo tabulky.)

Poznámka: Uživatelé oblíbeného balíčku django-debug-toolbar může dostat chybu v panelu SQL pro dotazy složené pomocí psycopg.sql.SQL() . Oprava se očekává pro vydání ve verzi 2.0.

Nyní zkuste funkci spustit na users tabulka:

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

Skvělý! Dále se podívejme, co se stane, když tabulka neexistuje:

>>>
>>> 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"
                        ^

Funkce vyvolá UndefinedTable výjimka. V následujících krocích použijete tuto výjimku jako označení, že vaše funkce je v bezpečí před útokem Python SQL injection.

Poznámka: Výjimka UndefinedTable byl přidán ve verzi psycopg2 2.8. Pokud pracujete se starší verzí Psycopg, dostanete jinou výjimku.

Chcete-li to dát dohromady, přidejte možnost počítat řádky v tabulce do určitého limitu. Tato funkce může být užitečná pro velmi velké tabulky. Chcete-li to implementovat, přidejte LIMIT klauzule k dotazu spolu s parametry dotazu pro hodnotu limitu:

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

V tomto bloku kódu jste označili limit pomocí sql.Literal() . Stejně jako v předchozím příkladu psycopg při použití jednoduchého přístupu sváže všechny parametry dotazu jako literály. Při použití sql.SQL() , musíte každý parametr explicitně anotovat pomocí sql.Identifier() nebo sql.Literal() .

Poznámka: Specifikace Python API bohužel neřeší vazbu identifikátorů, pouze literály. Psycopg je jediný populární adaptér, který přidal možnost bezpečně skládat SQL s literály i identifikátory. Díky této skutečnosti je ještě důležitější věnovat zvýšenou pozornost při vázání identifikátorů.

Spusťte funkci, abyste se ujistili, že funguje:

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

Nyní, když vidíte, že funkce funguje, ujistěte se, že je také bezpečná:

>>>
>>> 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...
                            ^

Tento traceback ukazuje, že psycopg unikla hodnotě a databáze ji považovala za název tabulky. Protože tabulka s tímto názvem neexistuje, UndefinedTable byla vznesena výjimka a nebyli jste napadeni!



Závěr

Úspěšně jste implementovali funkci, která skládá dynamické SQL bez vystavuje váš systém riziku pro Python SQL injection! Ve svém dotazu jste použili literály i identifikátory, aniž byste ohrozili zabezpečení.

Dozvěděli jste se:

  • Co Python SQL injection je a jak jej lze zneužít
  • Jak zabránit vkládání jazyka Python SQL pomocí parametrů dotazu
  • Jak bezpečně vytvářet příkazy SQL které jako parametry používají literály a identifikátory

Nyní můžete vytvářet programy, které odolají útokům zvenčí. Jděte do toho a překazte hackery!



  1. Proč žádný výstup, když se dokončí anonymní blok PLSQL?

  2. Nejlepší způsob, jak zkrátit řetězec UTF8 na základě délky bajtu

  3. CHYBA:neexistuje žádné jedinečné omezení odpovídající daným klíčům pro odkazovaný pruh tabulky

  4. Skutečný únikový řetězec a PDO