今天想和大家聊聊在軟體開發的資安中,注入攻擊一直是榜上常見的攻擊手段,在開發中若能夠提前擁有注入攻擊的觀念,在設計上可以更好的預防這類型網頁常見的注入攻擊,本篇文章會簡介什麼是網站資安上常見的注入攻擊,並且應該要如何去防範這類型造成的風險。
什麼是注入攻擊 Injection Attack
注入攻擊發生在攻擊者可以透過API/ url?
查詢等方式來注入一些網站開發者沒有預期的指令,比如說你今天打一個Python API Server的API,並且會執行下方的程式碼,下方Python程式碼舉例,某API會去目標主機上執行指令並建立一個檔案 filename.txt
import asyncio
async def my_command(filename) -> None:
process = await asyncio.create_subprocess_shell(
f"cd /myFolder; touch {filenme}.txt"
stderr=asyncio.subprocess.PIPE,
)
_, stderr = await process.communicate()
if stderr:
print(stderr.decode("utf-8"))
raise
而這時如果妳是攻擊者,假如攻擊者惡意傳入參數filename=bla.txt | cat /etc/passwd
或是bla | psql -c "DROP TABLE users;
透過這行指令,你會發現使用者竟然可以透過API來暴露出你這台主機的密碼或是資料被清空或操控了!而這就是典型的注入攻擊。
An injection attack is a form of cyberattack in which information is sent to alter the system’s interpretation of commands
如何預防注入攻擊
- Command Injection Attack
舉凡涉及到直接OS操作的或是database操作等功能,都要注意不要直接把User能控制的Command直接複製貼上到執行指令中,比如上方的範例程式碼之中,會建議不要用Shell的方式直接操作,而是可以使用Python內包的API,以上述例子就可以使用asyncio
裡的另一種function create_subprocess_exec
,透過傳入個別參數的方式來克制可能的跳脫字元,個別傳入的filename
會被視為是一個獨立變數,而不是一系列的指令,也就是說bla.txt | cat /etc/passwd
會變成 'bla.txt | cat /etc/passwd'
,而最終執行的指令會變成touch 'bla.txt | cat /etc/passwd.txt'
,透過這樣子的方法就可以避免類似的注入攻擊。
async def my_command(filename) -> None:
command = ["cd", "/MyFolder", "touch", f"{filename}.txt"]
process = await asyncio.create_subprocess_exec(
*command,
stdin=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
_, stderr = await process.communicate()
if stderr:
print(stderr.decode("utf-8"))
raise
另外,Python也提供了shlex
的library來“消毒”傳入的參數,假如你真的不得已必須要使用shell來執行指令的話,你也可以透過下列方式來避免Injection attack。
async def run_cmd(host: str, port: int = 22, username: str, password: str, command: str):
command = " ".join(("ls", "-la", shlex.quote(command))) # shlex.quote 可以避免指令套用跳脫字元 -> |
print(command)
async with asyncssh.connect(
host,
port=port,
username=username,
password=password,
) as conn:
result = await conn.run(command)
print(result.stdout)
asyncio.run(run_cmd(host, port, username, password, "| cat /etc/passwd")) # 安全
# ls -la '| cat /etc/passwd'
2. SQL Injection Attack
下方程式中原本是預期使用者輸入username來在database做查詢,但是攻擊者可能可以輸入"1 OR 1=1" --,最終你查詢的條件就會變成
SELECT id, username, password FROM users=1 or 1=1;
--
: 本身是SQL裡的註釋語法,所以他可以忽略你後面需要的參數。
欸嘿,直接暴露了所有使用者的密碼。
import (
"database/sql"
_ "github.com/lib/pq"
)
func getUser(ctx context.Context, username string) ([]User, error) {
db, _ := sql.Open("postgres", "user=username password=password dbname=yourdbname sslmode=disable")
cmd := "SELECT id, username, password FROM users where username ='" + username+"'
row, err := db.Query(cmd)
...
}
下方中,使用了事先定義好的SQL語法,並限制username $1,透過這種方法可以避免上方SQL拼接的問題,就算攻擊者使用上方的hacked way來執行操作,指令也只會變成
SELECT id, username, password FROM users='1 or 1=1';
import (
"database/sql"
_ "github.com/lib/pq"
)
const getUser = `-- name: GetItem: one
SELECT id, username, password FROM users
WHERE username = $1
`
func getUser(ctx context.Context, username string) ([]User, error) {
db, _ := sql.Open("postgres", "user=username password=password dbname=yourdbname sslmode=disable")
row, err := db.QueryContext(ctx, getUser, username)
...
}
所以,要預防Injection Attack,有以下幾點可以參考
1. 制定特定能被傳入的白名單指令,並僅限能夠傳入特地指令
2. 不要讓攻擊者可以直接傳入並執行指令,針對任何的特殊跳脫字元諸如 ||
, ,
, ;
, --
等做字元轉換
3. 定義使用者權限,比如只允許使用者讀取,嚴禁更新、刪除等操作!
4. 也可使用參數化的介面來規範傳入值,
以上就是針對注入攻擊的介紹啦,希望能與大家一同學習並精進自己在開發之路上面的品質!✌️✌️✌️
我們下次見囉~~