Injection Attack 瞭解注入攻擊

249

今天想和大家聊聊在軟體開發的資安中,注入攻擊一直是榜上常見的攻擊手段,在開發中若能夠提前擁有注入攻擊的觀念,在設計上可以更好的預防這類型網頁常見的注入攻擊,本篇文章會簡介什麼是網站資安上常見的注入攻擊,並且應該要如何去防範這類型造成的風險。

什麼是注入攻擊 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

如何預防注入攻擊

  1. 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. 也可使用參數化的介面來規範傳入值,

以上就是針對注入攻擊的介紹啦,希望能與大家一同學習並精進自己在開發之路上面的品質!✌️✌️✌️

我們下次見囉~~

參考連結

Command injection prevention for Python

LEAVE A REPLY

Please enter your comment!
Please enter your name here