🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

因此,這次我會分享我對於網路服務中的漏洞利用攻擊及防範方法,以及為什麼不正存取會被發現的看法。

想定讀者

  • 將要在企業接受訓練的人
  • 資訊相關科系的大學生及專科生

注意

這裡所寫的內容並非倡導不正存取,而是總結如何防範不正存取的方法。
除自製網站或公司內部測試,自行進行不正存取或封包擷取都屬於違法行為,請注意。

已有的設計論

事實上,資訊處理技術士考試中聞名的(這樣說可能會讓人誤解我以此為主業,但實際上並非如此)IPA已經製作了《安全的網站製作方式》這個網站。

此網站上記載了各種Web的注入類型及對策等,並非以程式碼的方式表達。
image.png
這是很常在資訊處理技術士考試中看到的內容(雖然我自己並沒有參加過)。

那麼有對策的網站和沒有對策的網站究竟有什麼不同呢?我將使用封包擷取軟體WireShark以及假設的網頁來進行測試。
順便一提,這是一個類似於學生用討論板的網站。
image.png

無脆弱性對策

程式碼

所使用的框架是Python的Flask。Flask與Django不同,需要逐一實作功能,因而非常適合用於這種學習,且在轉換到其他語言的網頁框架時也能保持相似的感覺(不過PHP的資料庫連接尚屬繁瑣)。
以下是部分程式碼摘錄。

@app.route("/thread2", methods=["GET", "POST"])
def thread2():
    if "email" in session:
        if request.method == "GET":
            thread_id = request.args.get("id")
            addr = request.remote_addr
            now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            con = sql.connect("data.db")
            cur = con.cursor()
            cur.execute("INSERT INTO log (ip, email, uri, method, time) VALUES (?, ?, ?, ?, ?)", (addr, session["email"], "/thread2?id=%s" % thread_id, "GET", now))
            con.commit()
            con.close()
            con = sql.connect("data.db")
            cur = con.cursor()
            cur.execute("SELECT team, num FROM comment WHERE thread_id=%s" % thread_id)
            res = ""
            for team, num in cur:
                res += team + "  " + str(num) + "<br>\n"
            con.close()
            return render_template("thread2.html", res=res, id=thread_id)
        elif request.method == "POST":
            thread_id = request.form["id"]
            team = request.form["team"]
            num = request.form["num"]
            addr = request.remote_addr
            now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            con = sql.connect("data.db")
            cur = con.cursor()
            cur.execute("INSERT INTO log (ip, email, uri, method, time) VALUES (?, ?, ?, ?, ?)", (addr, session["email"], "/thread2{id=%s, team=%s, num=%s}" % (thread_id, team, num), "POST", now))
            con.commit()
            con.close()
            con = sql.connect("data.db")
            cur = con.cursor()
            cur.execute("INSERT INTO comment (thread_id, team, num) VALUES (%s, '%s', '%s')" % (thread_id, team, num))
            con.commit()
            con.close()
            return redirect("thread2?id=" + html.escape(thread_id))
    else:
        return redirect("login-select")
<a href="logout">登出</a><br>
<a href="home">回首頁</a><br>
<form action="thread2" method="POST">
    班   名<input type="text" name="team"><br>
    學生證號<input type="text" name="num"><br>
    <input type="hidden" name="id" value="{{ id | safe }}">
    <input type="submit" value="發表">
</form>

{{res | safe}}

就這樣,Web程式設計的初學者常常會直接將字串串接反映在HTML或資料庫中,這其實是個很危險的做法。
那麼這樣做有什麼風險呢,讓我來解釋一下。

XSS(跨站腳本攻擊)

XSS簡單來說就是在輸入欄位反映到網站文本或資料庫字串中時,埋入JavaScript程式碼,使得輸入者所寫的JavaScript被執行。
例如在輸入欄位中輸入

<script>alert("XSS");</script>

那麼將會顯示為這樣。
image.png
從封包擷取中可以看到
image.png
HTML檔案內嵌入了JavaScript。
那麼這樣可以做什麼呢,惡意利用JavaScript可以進行各種操作。例如,使使用者被轉到錯誤的頁面,或使用XMLHttpRequest將會話金鑰的信息發送到外部伺服器,進而洩露登入資訊。

SQL注入

SQL注入則是透過在文本框中插入SQL中常用的「'(結束字串)」、「 -- (註解)」或「;(結束處理)」等,使得原本的SQL執行異常。
例如在這個網站中,有兩個文本框,透過填寫其中一個可以達成SQL注入。
例如在第一個文本框中填入
image.png

', (SELECT passwd FROM users WHERE email LIKE '%[email protected]%')) -- 

這樣製作的查詢會使得原本的

INSERT INTO comment (thread_id, team, num) VALUES (%s, '%s', '%s') % (thread_id, team, num)

變成

INSERT INTO comment (thread_id, team, num) VALUES (%s, '%s', (SELECT passwd FROM users WHERE email LIKE '%[email protected]%')) -- ', '%s') % (thread_id, team, num)

從而引出「[email protected]」這個用戶的密碼。
執行這樣的操作會得到如下結果。
image.png
這裡將密碼存成哈希值,因此一般來說是不會洩漏的,但如果平文的密碼存入資料庫,則可能會被洩漏,進而加入不法存取者的密碼列表(雖然現在Chrome自動生成密碼,但至少在這個網站上使用相同密碼仍是危險的)。

CSRF

這個詞可能不太為人所熟悉。其實沒有做對策的網站也不少見。
這種攻擊手法是將Form標籤的action部分填入不法想要針對的網站URL,誘使攻擊目標在登入狀態下訪問詐騙網站,然後隨便點擊一下就將資料發送出去。
透過這可以做什麼呢,或許大家都曾經遇到過,社交網路或討論區裡出現了你根本不曾寫過的內容。而在最糟的情況下(儘管多重驗證不容易發生)還有可能發生向金融機構的不正轉帳。
舉個例子,我們可以這樣製作一個詐騙網站。

<h1>恭喜你,中獎了</h1>
請馬上按下下面的按鈕來獲得獎品
<form method="POST" action="http://192.168.1.141/thread2">
    <input type="hidden" name="id" value="395">
    <input type="hidden" name="team" value="ネオ麦茶">
    <input type="hidden" name="num" value="m9(^A^)">
    <input type="submit" value="獲得獎品">
</form>

image.png
雖然在表現上看起來並不明顯,但從Form標籤的Hidden中能看出很多不應有的字串。
而點擊後就會導致
image.png
發生一鍵送出的情況。

脆弱性對策

那麼,針對這些問題該如何處理呢,接下來我會基於Python代碼進行說明。

@app.route("/thread", methods=["GET", "POST"])
def thread():
    if "email" in session:
        if request.method == "GET":
            thread_id = request.args.get("id")
            addr = request.remote_addr
            now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            con = sql.connect("data.db")
            cur = con.cursor()
            cur.execute("INSERT INTO log (ip, email, uri, method, time) VALUES (?, ?, ?, ?, ?)", (addr, session["email"], "/thread?id=%s" % thread_id, "GET", now))
            con.commit()
            con.close()
            token = secrets.token_hex()
            session["thread"] = token
            con = sql.connect("data.db")
            cur = con.cursor()
            cur.execute("SELECT team, num FROM comment WHERE thread_id=?", (thread_id,))
            res = ""
            for team, num in cur:
                res += html.escape(team) + "  " + html.escape(str(num)) + "<br>\n"
            con.close()
            return render_template("thread.html", res=res, id=thread_id, token=token)
        elif request.method == "POST":
            if request.form["thread"] == session["thread"]:
                thread_id = request.form["id"]
                team = request.form["team"]
                num = request.form["num"]
                addr = request.remote_addr
                now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                con = sql.connect("data.db")
                cur = con.cursor()
                cur.execute("INSERT INTO log (ip, email, uri, method, time) VALUES (?, ?, ?, ?, ?)", (addr, session["email"], "/thread{id=%s&team=%s&num=%s}" % (thread_id, team, num), "POST", now))
                con.commit()
                con.close()
                con = sql.connect("data.db")
                cur = con.cursor()
                cur.execute("INSERT INTO comment (thread_id, team, num) VALUES (?, ?, ?)", (thread_id, team, num))
                con.commit()
                return redirect("thread?id=" + html.escape(thread_id))
            else:
                return "<h1>不正的存取</h1>"
    else:
        return redirect("login-select")

那麼我們來逐一看看。

CSRF對策

這個有對策的網站在顯示時的封包擷取是這樣的。
image.png
仔細觀察會發現有一串十六進位的長字串,這是進行POST操作前的隨機一次性密碼。
這個隨機生成的字串會同時存到HTML的Hidden和會話中。

if request.form["thread"] == session["thread"]:
  ...

這樣可以有效防止外部不法寫入。
這個字串的生成方法可以是:

import secrets
...
            token = secrets.token_hex()
            session["thread"] = token
            return render_template("thread.html", res=res, id=thread_id, token=token)

這樣,每次POST操作都會通過隨機生成的十六進位字串驗證。
因此外部網站很難猜到隨機字串,並且不知道這個參數的存在,這樣請求就會出錯。
image.png

XSS對策

res += html.escape(team) + "  " + html.escape(str(num)) + "<br>\n"

這裡沒有將字串直接串接,而是使用「html.escape(字串)」進行處理,這樣可以將「<」、「>」等標籤和「"」等字串轉換為能夠正常顯示在瀏覽器的格式。
我們來看看實際結果。
image.png
最終的結果會以這樣的格式顯示,而從封包擷取中可以看到
image.png
確認是安全的。

SQL注入對策

這部分簡單點,資料庫相關的庫中都包含有佔位符(Placeholder)這種清理工具。

                con = sql.connect("data.db")
                cur = con.cursor()
                cur.execute("INSERT INTO comment (thread_id, team, num) VALUES (?, ?, ?)", (thread_id, team, num))
                con.commit()

這樣就能防止SQL注入,實際上當我們輸入之前的字串時,
image.png
會發現打入的SQL會照原樣顯示。

日誌追蹤

也許有些人會想,這樣防範就可以了,然而自己建置的網站安全性並不總是能夠保證。也許在某些地方你會忽略防範。因此,查看日誌非常重要。

cur.execute("INSERT INTO log (ip, email, uri, method, time) VALUES (?, ?, ?, ?, ?)", (addr, session["email"], "/thread{id=%s&team=%s&num=%s}" % (thread_id, team, num), "POST", now))

例如這樣的方式將IP地址、用戶ID、URL、方法和時間記錄在日誌中。
接著可以查詢IP地址。

            con = sql.connect("data.db")
            cur = con.cursor()
            cur.execute("SELECT ip FROM log GROUP BY ip")
            for ip in cur:
                res = res + html.escape(str(ip[0])) + "<br>"
            con.close()

這樣就能生成伺服器曾經訪問過的IP地址清單。
image.png
在這裡可以針對被報告的IP地址進行搜索。
這樣就能看到類似於如上的結果。
image.png
image.png
透過這樣的方式,你可以發現潛在的惡意輸入。此外,若是有用戶使用不正當方式登入,且未執行正常的登出或其他正常手段時,可以懷疑其為會話金鑰劫持行為。
因此,將這樣的日誌保留是非常有用的。

總結


原文出處:https://qiita.com/murasaki1994/items/81fabafaaa1fc0e8fade


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝19   💬3  
490
🥈
我愛JS
📝1   💬5   ❤️2
65
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付