有時候,程式開發會使用參數方式組合出 SQL 指令,再往資料庫提請求。但是如果設計有疏忽,沒有對前端使用者的輸入條件進行完整的檢查,或是資料庫維護者不小心忘記限制好帳號的權限,可能會被有心人士設計出恰當的輸入條件,湊出能逾越一般該有權限所能進行的資料庫指令,這一般被稱作 SQL Injction 攻擊。
PostgreSQL 企業版提供了資料庫端的資料防護機制,SQL/Protect,能夠在資料庫端防止異常的 SQL,例如,對於資料需要受保護的資料表進行資料「全部」撈出來的 SQL SELECT 指令,或是怪怪的條件等等。
SQL/Protect 模組,當我們選定一些資料庫帳戶 User 或是帳戶群組 Group Role(以上兩者,在 PostgreSQL 裡面統稱為 Role ~)之後,能提供以下的功能:
- 被動紀錄、警示列管帳戶執行可疑活動
- 主動防止列管帳戶執行可疑活動
而「可疑活動」的界定,則是透過學習模式,預先將該帳號會進行的行為全部演練一次。例如,該帳號會對哪幾個表格輸入/查詢,查詢用到的 SQL 有哪一些型態,等等,全部都預先在 SQL/Protect 學習模式下跑一遍。建立起該帳號的行為比對模式(能存取的表格,能進行的作業,等等)。
學習完之後,只要該帳號發生紀錄過的行為模式「以外」,就會觸發 SQL/Protect 功能。因此這個工具的使用場景,便是針對提供 Application Server 使用帳號進行保護:這類帳號的行為固定,都是由已經開發好的程式進行活動;發生了一般模式之外的行徑,八成是被攻擊,就會受 SQL/Protect 所保護。
以下便紀錄基本的使用筆記:
使用 SQL/Protect 的流程如下:
- 設定預先載入 SQL/Protect 模組
- 先啟動 SQL/Protect 學習模式
- 設置好需要受管理的資料庫帳號/群組
- 進行該帳號需要的進行的「正常」作業,使 SQL/Protect 紀錄到該帳號「所有可能行為」
- 完成學習,調整成被動模式/主動模式,開始保護
啟用 SQL/Protect 模組,並設定保護模式:這些要在 $PGDATA/postgresql.conf 裡面設定:
bash-$ source /opt/PostgresPlus/9.5AS/pgplus_env.sh bash-$ sed -e "s@shared_preload_libraries = '\([^']*\)'@shared_preload_libraries = '\1,\$libdir\/sqlprotect'@g" \ -i $PGDATA/postgresql.conf bash-$ cat << CFGEOF >> $PGDATA/postgresql.conf #--------------------------------------------------------------------------- # EDB SQL/Protect Module #--------------------------------------------------------------------------- edb_sql_protect.enabled = on # Reload to change edb_sql_protect.level = learn # Reload to change # 'learn','passive', or 'active' edb_sql_protect.max_protected_roles = 64 # Default : 64 edb_sql_protect.max_protected_relations = 1024 # Default : 1024 edb_sql_protect.max_queries_to_save = 5000 # Default : 5000 CFGEOF bash-$ pg_ctl restart
接著在要啟動 SQL/Protect 的 Database 裡面建立相關物件,在此登入 edb 進行試驗
bash-$ psql -p 5444 -d edb -f /opt/PostgresPlus/9.5AS/share/contrib/sqlprotect.sql Password: CREATE SCHEMA GRANT SET CREATE TABLE GRANT CREATE TABLE GRANT CREATE FUNCTION CREATE FUNCTION CREATE FUNCTION CREATE FUNCTION CREATE FUNCTION CREATE FUNCTION CREATE FUNCTION DO CREATE FUNCTION CREATE FUNCTION DO CREATE VIEW GRANT DO CREATE VIEW GRANT CREATE VIEW GRANT CREATE FUNCTION CREATE FUNCTION SET
指定要被 SQL/Protect 監管的帳號,然後列舉一下狀態
edb=# CREATE USER someuser PASSWORD 'password'; CREATE ROLE edb=# SET search_path TO sqlprotect; SET edb=# SELECT protect_role('someuser'); protect_role -------------- (1 row) edb=# SELECT * FROM edb_sql_protect; dbid | roleid | protect_relations | allow_utility_cmds | allow_tautology | allow_empty_dml -------+---------+-------------------+--------------------+-----------------+----------------- 14845 | 2877024 | t | f | f | f (1 row)
上面可以看到,目前這個帳號受到控制的有
- 限制表格存取(僅允許學習模式時紀錄到的表格)(protect_relations)
- 執行 DDL(allow_utility_cmds)
- 防止全真條件語句(allow_tautology)
- 沒有 WHERE 條件限制的 DML(allow_empty_dml)
在這裡提醒一點,Super user 帳號無法被 SQL/Protect 模組列管
edb=# SELECT protect_role('enterprisedb'); ERROR: super user can not be a protected user
接著建立資訊以便供 SQL/Protect 學習。
- 測試表和資料
edb=# CREATE TABLE public.protect_tbl(i int,str text); CREATE TABLE edb=# DO $$ edb$# BEGIN edb$# FOR a IN 1..1000 LOOP edb$# INSERT INTO protect_tbl VALUES (a, 'ooooooooooooooo'); edb$# END LOOP; edb$# END $$; DO edb=# GRANT ALL ON protect_tbl TO someuser; GRANT
- 用該受監控帳號,進行他的正常活動
edb=# \c edb someuser Password for user someuser: You are now connected to database "edb" as user "someuser". edb=> INSERT INTO protect_tbl(i, str) VALUES(2000,'hello'); NOTICE: SQLPROTECT: Learned relation: 2880349 INSERT 0 1 edb=> UPDATE protect_tbl SET str = 'happy' WHERE i = 2; UPDATE 1 edb=> SELECT * FROM protect_tbl WHERE i = 2; i | str ---+------- 2 | happy (1 row) edb=> \q
然後把學習模式關掉,設置成主動防護模式
bash-$ sed -e "s@edb_sql_protect.level = learn@edb_sql_protect.level = active@g" \ -i $PGDATA/postgresql.conf bash-$ pg_ctl reload server signaled
接著開始展示功能。
edb=# \c edb someuser Password for user someuser: You are now connected to database "edb" as user "someuser". edb=> select * from public.protect_tbl where 1=1; ERROR: SQLPROTECT: Illegal Query: tautology
採集一下 SQL/Protect 紀錄
edb=> \c edb enterprisedb Password for user enterprisedb: You are now connected to database "edb" as user "enterprisedb". edb=# SELECT * FROM sqlprotect.edb_sql_protect_queries; username | ip_address | port | machine_name | date_time | query ----------+------------+------+--------------+---------------------------+--------------------------------------------------------- someuser | | | | 06-FEB-17 13:10:00 +00:00 | select * from public.protect_tbl where 1=1; (1 row) edb=#
edb=# \c edb someuser Password for user someuser: You are now connected to database "edb" as user "someuser". edb=> drop table public.protect_tbl; ERROR: SQLPROTECT: This command type is illegal for this user edb=> truncate protect_tbl ; ERROR: SQLPROTECT: This command type is illegal for this user edb=> create table public.test_tbl(i int); ERROR: SQLPROTECT: This command type is illegal for this user
採集 SQL/Protect 紀錄
edb=> \c edb enterprisedb Password for user enterprisedb: You are now connected to database "edb" as user "enterprisedb". edb=# SELECT * FROM sqlprotect.edb_sql_protect_queries; username | ip_address | port | machine_name | date_time | query ----------+------------+------+--------------+---------------------------+--------------------------------------------------------- someuser | | | | 06-FEB-17 13:10:00 +00:00 | select * from public.protect_tbl where 1=1; someuser | | | | 06-FEB-17 13:12:00 +00:00 | drop table public.protect_tbl; someuser | | | | 06-FEB-17 13:41:00 +00:00 | truncate protect_tbl ; someuser | | | | 06-FEB-17 13:26:00 +00:00 | create table public.test_tbl(i int); (4 rows) edb=#
3. 最後,針對 UPDATE 和 DELETE 這兩種 DML,能夠避免沒有附上限制條件的狀況下被執行。以下把 DELETE 相關的狀況都執行一次:
edb=> DELETE FROM protect_tbl WHERE i=1; DELETE 1 edb=> DELETE FROM protect_tbl WHERE 1=1; ERROR: SQLPROTECT: Illegal Query: tautology edb=> DELETE FROM protect_tbl; ERROR: SQLPROTECT: Illegal Query: empty DML edb=> DELETE FROM protect_tbl WHERE str IS NOT NULL; DELETE 1004
進資料庫確認一下,並採集 SQL/Protect 紀錄
edb=> \c edb enterprisedb Password for user enterprisedb: You are now connected to database "edb" as user "enterprisedb". edb=# SELECT * FROM sqlprotect.edb_sql_protect_queries; username | ip_address | port | machine_name | date_time | query ----------+------------+------+--------------+---------------------------+--------------------------------------------------------- someuser | | | | 06-FEB-17 13:10:00 +00:00 | select * from public.protect_tbl where 1=1; someuser | | | | 06-FEB-17 13:12:00 +00:00 | drop table public.protect_tbl; someuser | | | | 06-FEB-17 13:41:00 +00:00 | truncate protect_tbl ; someuser | | | | 06-FEB-17 13:26:00 +00:00 | create table public.test_tbl(i int); someuser | | | | 06-FEB-17 13:29:00 +00:00 | DELETE FROM protect_tbl WHERE 1=1; someuser | | | | 06-FEB-17 13:30:00 +00:00 | DELETE FROM protect_tbl; (6 rows) edb=#
SQL/Protect 功能目前到這裡展示完畢。
截至 9.5 版為止,發現 SQL/Protect 的問題:
- SQL/Protect 沒辦法阻擋 SELECT * FROM 這個全表格的查詢。
不過,這個功能可以透過企業版的 Virtual Private Database 功能,或是 PostgreSQL 9.5 版之後引入的 Row-Level Security 功能來達成相似功能。
- SQL/Protect 沒辦法避免稍微奇怪一點的限制條件。例如,WHERE str IS NOT NULL; 這種條件。
- SQL/Protect 的 Utility Command 基本上把受管控帳號的行為限制在 SELECT,INSERT,UPDATE,和 DELETE:像是 COPY 指令也不算數。
edb=> copy protect_tbl TO STDOUT (DELIMITER '|'); ERROR: SQLPROTECT: This command type is illegal for this user
後記
聽說現在的開發架構都比較能夠在開發上避免 SQL Injection 的樣子,我想這個「資料庫防火牆」功能就當成買保險的樣子來用就好了~
參考資料:
幾個對於 PostgreSQL 檢查 SQL Injection 的 Cheet Sheet
sql_firewall 專案:相關介紹 A Hacker's Diary: sql_firewall: a SQL Firewall Extension for PostgreSQL。設計上也是分成三種模式,功能與 SQL/Protect 不同,以學習 Query Statement 達成防堵 SQL Injection 功能。
不過截至 0.8.1 版,這個模組會全域都有作用,不太適合正式使用。
另外,有一個「好像」相似的工具,叫做 GreenSQL,只是看來沒在維護了
沒有留言:
張貼留言