Minghsin University of Science and Technology
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

1

Local AI

--

Uedu Code

--

明新科大 AQI 70 24°C PM2.5 19
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 15

安全性

root 密碼、匿名帳號、最小權限與連線安全
Interaction實作導覽 20 minute(s)

Learning Objectives

  • 說明 mysql_secure_installation 做了哪些事,以及 Ubuntu 上 root 用 auth_socket 登入的意義
  • 找出並用 DROP USER 移除匿名帳號,說明匿名帳號為什麼會讓其他人登入成錯的帳號
  • 依最小權限原則替應用程式建立帳號,不讓程式使用 root
  • 說出 FILE、PROCESS、WITH GRANT OPTION 的風險,以及 secure_file_priv 如何限制檔案讀寫
  • 用 bind-address、防火牆與密碼政策保護連線,並用參數化查詢防止 SQL Injection

資料庫被入侵的常見途徑

資料庫通常存著整個系統最有價值的資料:會員帳號、訂單、成績。實際發生的資料外洩,多半不是因為高深的技術,而是幾個基本設定沒做好:

途徑 例子 本課的對策
沒有密碼或密碼太弱的帳號 教學用安裝包的 root 沒有密碼、留著匿名帳號 執行 mysql_secure_installation、移除匿名帳號、密碼政策
權限太大的帳號 網站程式用 root 連線,程式一有漏洞,整台資料庫都被拿走 最小權限原則
資料庫直接暴露在網路上 3306 連接埠對整個網際網路開放,被自動化程式不斷猜密碼 bind-address、防火牆
程式把使用者輸入直接拼進 SQL 登入表單輸入 ' OR '1'='1 就登入成功 參數化查詢

投影片使用的 XAMPP 是方便教學的安裝包,root 預設沒有密碼,投影片也提醒「除了教學實驗環境外,建議設密碼」。 本課以 Ubuntu 24.04 的 MySQL 8.0 為準,它的預設值已經比當年安全很多,但仍然有幾件事要自己做。

root 帳號與 mysql_secure_installation

Ubuntu 用 apt install mysql-server 安裝後,root@localhost 預設使用 auth_socket 驗證: 不檢查密碼,而是檢查「連線的作業系統使用者是不是 root」。所以要用 sudo mysql 登入,一般使用者即使知道帳號名稱也登不進去, 而且 root 只能從本機登入。這比「root 設一組密碼」更安全,不要為了方便把它改成密碼登入,更不要建立 'root'@'%'。

mysql_secure_installation 是 MySQL 附的安全設定精靈,安裝後執行一次,它會依序詢問:

  1. 是否啟用 VALIDATE PASSWORD 元件:之後設定的密碼必須達到長度、大小寫、數字、符號等強度要求
  2. 設定 root 密碼(Ubuntu 的 root 使用 auth_socket,會略過這一步)
  3. 移除匿名帳號
  4. 禁止 root 從其他主機登入
  5. 刪除任何人都能存取的 test 資料庫
  6. 重新載入權限表

投影片的舊寫法 set password for root@localhost = password('…') 在 8.0 已經不能用(PASSWORD() 函式已移除)。 需要替帳號改密碼時用 ALTER USER '帳號'@'主機' IDENTIFIED BY '新密碼';,改自己的密碼可以寫 ALTER USER USER() IDENTIFIED BY '…';。

導覽:執行 mysql_secure_installation

在剛安裝好 MySQL 的 Ubuntu 伺服器上執行。輸出有節錄,「…」代表省略的說明文字;帳號清單是示意,依安裝版本可能略有不同。

  1. 1

    啟動精靈並啟用密碼強度檢查

    選擇密碼強度等級:0 LOW 只檢查長度、1 MEDIUM 還要求數字、大小寫與特殊符號、2 STRONG 再加上字典檢查。一般選 1。

  2. 2

    移除匿名帳號、禁止 root 遠端登入、刪除 test 資料庫

    這幾題都回答 y。Ubuntu 的 MySQL 8.0 安裝後本來就沒有匿名帳號與 test 資料庫,但回答 y 可以確保它們不存在。

  3. 3

    檢查有哪些帳號

    root 的 plugin 是 auth_socket。debian-sys-maint 是 Ubuntu 套件維護用的帳號,密碼存在只有 root 能讀的 /etc/mysql/debian.cnf;mysql. 開頭的是 MySQL 內部帳號,已經鎖定、無法登入。user 欄位是空字串的列就是匿名帳號,這裡沒有。

這段導覽完成了

匿名帳號為什麼危險

匿名帳號是使用者名稱為空字串的帳號,例如 ''@'localhost':任何人用任何名稱都能以它登入。 MySQL 5 時代的安裝程式(包括 XAMPP)常會建立匿名帳號,方便測試。

匿名帳號還有一個不容易察覺的問題。MySQL 收到連線時,會把 mysql.user 依「主機寫得越具體越優先」排序,再找第一個符合的帳號。 假設有兩個帳號:'laura'@'%' 和 ''@'localhost'。laura 從本機登入時,localhost 比 % 具體, 所以 MySQL 選中的是匿名帳號 ''@'localhost',不是 laura 自己的帳號。laura 會發現自己的密碼不對、或是登入後沒有任何權限。 登入後執行 SELECT CURRENT_USER(); 可以看到伺服器實際選中的帳號(USER() 則是你連線時報的名稱)。

移除匿名帳號: 投影片的寫法是直接刪除權限表格的資料列:

DELETE FROM mysql.user WHERE User = '';      -- 舊寫法
DELETE FROM mysql.db WHERE User = '';
FLUSH PRIVILEGES;

MySQL 8.0 不建議直接修改權限表格:權限分散在 user、db、tables_priv 等多張表格,漏刪一張就會留下殘餘的權限,而且忘了 FLUSH PRIVILEGES 就不會生效。 8.0 一律用 DROP USER,它會一次清掉這個帳號在所有權限表格的資料,並且立即生效:

SELECT user, host FROM mysql.user WHERE user = '';   -- 先找出有哪些匿名帳號
DROP USER ''@'localhost';                            -- 每一個都用 DROP USER 刪除

找出並移除匿名帳號

MySQL 8.0 不會自動建立匿名帳號,所以範例先在第 1 行建立一個,模擬從舊版升級上來、還留著匿名帳號的伺服器。注意 mysql.user 的結果裡,匿名帳號的 user 欄位是空白的。

最小權限原則:應用程式不用 root

最小權限原則(principle of least privilege):每個帳號只給完成工作所需的最少權限。 網站程式在日常運作中只需要讀寫資料,不需要刪除表格、建立帳號、讀寫伺服器檔案,所以替它建立專用帳號:

CREATE USER 'webapp'@'localhost' IDENTIFIED BY '…一組長而隨機的密碼…';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'webapp'@'localhost';
  • 範圍限定在程式自己的資料庫(shop.*),不要用 *.*
  • 主機寫程式所在的主機:程式和 MySQL 在同一台就用 localhost,在另一台就寫那台的 IP,不要用 %
  • 建立表格、修改表格結構(部署新版本時)另外用一個管理用的帳號,不讓日常執行的程式擁有 CREATE、DROP、ALTER
  • 只讀報表的程式只給 SELECT
  • 每支程式各用一個帳號,出事時可以只停用那一個

這樣即使程式出現 SQL Injection 之類的漏洞,攻擊者也刪不掉表格、拿不到其他資料庫的資料、讀不到伺服器的檔案。

應用程式帳號能做與不能做的事

root 替網站程式建立 webapp 帳號,只給 employee 資料庫的四種資料操作權限。從第 5 行起以 webapp 身分連線(真實環境是程式用這組帳號密碼連線)。

想一想:帳號與權限

1.

網站程式和 MySQL 在同一台主機,程式只需要查詢、新增與修改 shop 資料庫的資料。哪一個帳號設定最適當?

2.

webapp 帳號擁有 GRANT SELECT, INSERT, UPDATE, DELETE ON employee.*。攻擊者透過程式漏洞,以 webapp 的身分執行了下面的敘述,結果是什麼?

DROP TABLE employee.assignment;
3.

伺服器上有 'laura'@'%' 和匿名帳號 ''@'localhost' 兩個帳號。laura 在資料庫伺服器本機執行 mysql -u laura -p,伺服器會用哪個帳號比對她的登入?

第 1 次作答

要特別小心的權限:FILE、PROCESS、WITH GRANT OPTION

投影片列出三種要謹慎授予的權限:

權限 能做什麼 風險
FILE SELECT … INTO OUTFILE 把資料寫成伺服器上的檔案;LOAD DATA INFILE、LOAD_FILE() 讀取伺服器上的檔案 讀取 /etc/passwd 等系統檔案、把資料庫內容整批匯出,甚至寫入檔案
PROCESS SHOW PROCESSLIST 看到所有連線正在執行的 SQL 別人查詢裡的資料(例如 WHERE 條件中的身分證字號)會被看到
WITH GRANT OPTION 把自己的權限轉授給別人 權限擴散,很難追蹤最後落到誰手上

此外,ALL PRIVILEGES ON *.*、CREATE USER、SUPER 與 SYSTEM_VARIABLES_ADMIN 這類管理權限,也只給資料庫管理員。

沒有 PROCESS 權限的帳號執行 SHOW PROCESSLIST,只會看到自己的連線(下面是本機真實 MySQL 8.0.44 的結果,Id 與連接埠是示意):

mysql> SHOW PROCESSLIST;
+----+------+-----------------+----------+---------+------+-------+------------------+
| Id | User | Host            | db       | Command | Time | State | Info             |
+----+------+-----------------+----------+---------+------+-------+------------------+
| 23 | fred | localhost:59701 | employee | Query   |    0 | init  | SHOW PROCESSLIST |
+----+------+-----------------+----------+---------+------+-------+------------------+

secure_file_priv 限制檔案讀寫的位置。 即使帳號有 FILE 權限,MySQL 8.0 也只允許在 secure_file_priv 指定的資料夾裡讀寫檔案:

secure_file_priv 的值 意思
一個資料夾,例如 /var/lib/mysql-files/ 只能在這個資料夾裡讀寫(Ubuntu 的預設)
空字串 任何 mysqld 有權限的位置都可以,不安全
NULL 完全禁止匯入匯出檔案

它是唯讀變數,只能在設定檔裡修改並重新啟動。寫到其他位置會出現 ERROR 1290;檔案已經存在時出現 ERROR 1086,MySQL 不會覆蓋既有的檔案。

FILE 權限與 secure_file_priv

執行前已經建立 fred,只有 SELECT ON employee.*。fred 想把資料匯出成檔案,接著 root 試著寫到不同的位置。(模擬環境的檔案只存在記憶體裡,secure_file_priv 模擬 Ubuntu 的預設值。)

密碼與驗證方式

MySQL 不保存密碼本身,只保存雜湊值。 MySQL 8.0 預設的驗證方式是 caching_sha2_password(SHA-256 加上隨機的 salt), 存在 mysql.user 的 authentication_string 欄位(MySQL 5.6 以前的欄位叫 Password)。舊的 mysql_native_password 用的是不加 salt 的 SHA-1, 8.0.34 起列為不建議使用,8.4 預設停用,9.0 移除。舊程式或舊驅動程式連不上 8.0 時,應該更新驅動程式,而不是把帳號改回 mysql_native_password。

投影片提到「密碼可用 MD5() 或 CRYPT() 加密」,這是指在自己的應用程式裡保存使用者密碼。MD5 現在算得太快、容易被暴力破解, 應用程式應該使用專門的密碼雜湊函式庫(例如 bcrypt、Argon2),不要用 MD5 或 SHA 直接雜湊。

MySQL 8.0 可以替帳號加上密碼政策:

CREATE USER 'amy'@'localhost' IDENTIFIED BY 'Amy!2026x'
    PASSWORD EXPIRE INTERVAL 90 DAY          -- 90 天後要求更換密碼
    FAILED_LOGIN_ATTEMPTS 3 PASSWORD_LOCK_TIME 1;  -- 連續輸錯 3 次,鎖定 1 天
ALTER USER 'amy'@'localhost' ACCOUNT LOCK;   -- 暫時停用帳號(不刪除)
ALTER USER 'amy'@'localhost' ACCOUNT UNLOCK;

不要把密碼寫在指令裡。 mysql -u fred -pFr3d!2026 會讓密碼留在 shell 的指令紀錄,同一台主機的其他使用者也可能從程序清單看到; mysql 本身也會警告 mysql: [Warning] Using a password on the command line interface can be insecure.。 用 -p 後面不接密碼、讓它提示輸入;需要自動登入時,把帳號密碼寫在 ~/.my.cnf 的 [client] 區段並 chmod 600, 或用 mysql_config_editor set --login-path=… 存成加密的 ~/.mylogin.cnf。程式的資料庫密碼放在設定檔或環境變數,不寫進程式碼、不上傳到 Git。

網路存取:bind-address 與防火牆

上一課看過 Ubuntu 預設 bind-address = 127.0.0.1:MySQL 只接受本機的連線,網路上的其他主機連 3306 連接埠會被拒絕。 網站程式和 MySQL 在同一台主機時,維持這個設定就好。

程式在另一台主機時,才需要開放網路連線,而且要層層限制:

  1. bind-address 改成資料庫主機在內部網路的 IP(例如 10.0.0.10),不要用 0.0.0.0(所有網路介面)
  2. 防火牆只允許程式所在的主機連 3306,例如 sudo ufw allow from 10.0.0.5 to any port 3306 proto tcp;3306 不要對整個網際網路開放
  3. 帳號的主機寫成程式主機的 IP:'webapp'@'10.0.0.5',不要用 'webapp'@'%'
  4. 跨主機的連線要加密:MySQL 8.0 安裝時會自動產生 TLS 憑證,可以用 ALTER USER 'webapp'@'10.0.0.5' REQUIRE SSL; 規定這個帳號只能用加密連線

投影片中「建立可由另一台主機登入本機的帳號」的寫法 grant all on *.* to luke identified by 'password', 在 8.0 不但是語法錯誤,它的意思(luke 省略主機='luke'@'%',再給全部權限)也正好是上面每一點的反例。

SQL Injection:問題出在程式怎麼組 SQL

前面都在設定 MySQL 本身,但很多資料外洩發生在應用程式。假設網站的登入程式(Python)這樣寫:

# 錯誤示範:把使用者輸入的字串直接拼進 SQL
sql = "SELECT id, username FROM member WHERE username = '" + username + "' AND pin = '" + pin + "'"
cursor.execute(sql)

使用者正常輸入 alice 與 1234,送到 MySQL 的是:

SELECT id, username FROM member WHERE username = 'alice' AND pin = '1234'

但如果有人在兩個欄位都輸入 x' OR '1'='1,拼出來的 SQL 變成:

SELECT id, username FROM member WHERE username = 'x' OR '1'='1' AND pin = 'x' OR '1'='1'

輸入裡的單引號提早結束了字串,後面的 OR '1'='1' 變成 SQL 的一部分,而 '1'='1' 永遠成立,所有會員都被查出來,程式以為登入成功。 這就是 SQL Injection:使用者的輸入被當成 SQL 程式碼執行。

解法是參數化查詢(parameterized query):SQL 的結構先寫好,使用者輸入只當作「值」另外傳給資料庫驅動程式,由它負責正確地處理引號:

# 正確:用 %s 佔位,值放在第二個參數(PyMySQL、mysql-connector 的寫法)
sql = "SELECT id, username FROM member WHERE username = %s AND pin = %s"
cursor.execute(sql, (username, pin))

驅動程式會把輸入裡的單引號跳脫,送出的 SQL 中 x' OR '1'='1 只是一個奇怪的使用者名稱,查不到任何人。 注意:%s 要交給 execute() 的第二個參數處理,不能自己用 Python 的 % 或 f-string 把值填進字串,那樣仍然是拼接。

參數化查詢解決的是程式的問題,最小權限是第二道防線:即使某處漏掉了,攻擊者能做的事也被帳號的權限限制住。

同一個輸入,拼接與參數化的差別

這三個查詢就是上面三種情況送到 MySQL 的 SQL。執行前已經建立 shop.member,有 alice、bob、carol 三位會員(為了範例簡化,PIN 以明文存放;實際系統要存雜湊值)。觀察第二個查詢的 WHERE 那一步:AND 先於 OR 計算,條件變成「username = 'x'」或「'1'='1' 且 pin = 'x'」或「'1'='1'」,最後一項對每一列都成立。

想一想:檔案權限與 SQL Injection

1.

root 在 Ubuntu 預設設定(secure_file_priv 是 /var/lib/mysql-files/)的 MySQL 執行下面的匯出,結果是什麼?

SELECT * FROM employee.client INTO OUTFILE '/home/ubuntu/client.txt';
2.

下面哪一段 Python 程式碼可以防止 SQL Injection?(cursor 是 PyMySQL 的游標)

3.

member 有 alice、bob、carol 三位會員。登入程式用字串拼接,攻擊者在帳號欄輸入 carol、在 PIN 欄輸入 x' OR '1'='1,拼出的 SQL 如下。會傳回幾列?

SELECT COUNT(*) FROM member WHERE username = 'carol' AND pin = 'x' OR '1'='1';
第 1 次作答

動手寫:購物網站的最小權限帳號

購物網站的程式只會查詢、新增與修改客戶資料(employee.client 表格),不會刪除客戶。請以 root 身分:

  1. 建立帳號 'shop_app'@'localhost',密碼 Sh0p!2026
  2. 只授權 employee.client 表格的 SELECT、INSERT、UPDATE
  3. 改用 shop_app 連線(-- @user shop_app@localhost),依 clientID 排序查詢 clientID、name
  4. 再試著刪除 clientID 為 2 的客戶,確認會被擋下
你的輸出
預期輸出

重點整理:安全檢查清單

項目 做法
安裝後 執行 sudo mysql_secure_installation:密碼強度、移除匿名帳號與 test 資料庫、禁止 root 遠端登入
root Ubuntu 預設 auth_socket,用 sudo mysql 登入;不建立 'root'@'%',應用程式不用 root
匿名帳號 SELECT user, host FROM mysql.user WHERE user = '' 檢查,用 DROP USER ''@'主機' 移除(不要直接 DELETE 權限表格)
應用程式帳號 最小權限:只給需要的權限、只到自己的資料庫、主機寫程式所在的主機
危險權限 FILE、PROCESS、WITH GRANT OPTION、ALL ON *.* 只給管理員
檔案 secure_file_priv 維持在專用資料夾,不設成空字串
密碼 8.0 預設 caching_sha2_password;密碼不寫在指令與程式碼裡;可加到期與輸錯鎖定政策
網路 bind-address = 127.0.0.1;必須開放時只綁內部 IP、防火牆只允許特定主機、REQUIRE SSL
程式 一律用參數化查詢,不用字串拼接 SQL

已在 MySQL 8.0 移除、不要再用的寫法:GRANT … IDENTIFIED BY、SET PASSWORD = PASSWORD('…');直接 DELETE FROM mysql.user 加上 FLUSH PRIVILEGES 則是不建議的做法。

這一課完成了!
Next lesson:資料庫最佳化
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。