因此,這次我會分享我對於網路服務中的漏洞利用攻擊及防範方法,以及為什麼不正存取會被發現的看法。
這裡所寫的內容並非倡導不正存取,而是總結如何防範不正存取的方法。
除自製網站或公司內部測試,自行進行不正存取或封包擷取都屬於違法行為,請注意。
事實上,資訊處理技術士考試中聞名的(這樣說可能會讓人誤解我以此為主業,但實際上並非如此)IPA已經製作了《安全的網站製作方式》這個網站。
此網站上記載了各種Web的注入類型及對策等,並非以程式碼的方式表達。

這是很常在資訊處理技術士考試中看到的內容(雖然我自己並沒有參加過)。
那麼有對策的網站和沒有對策的網站究竟有什麼不同呢?我將使用封包擷取軟體WireShark以及假設的網頁來進行測試。
順便一提,這是一個類似於學生用討論板的網站。

所使用的框架是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簡單來說就是在輸入欄位反映到網站文本或資料庫字串中時,埋入JavaScript程式碼,使得輸入者所寫的JavaScript被執行。
例如在輸入欄位中輸入
<script>alert("XSS");</script>
那麼將會顯示為這樣。

從封包擷取中可以看到

HTML檔案內嵌入了JavaScript。
那麼這樣可以做什麼呢,惡意利用JavaScript可以進行各種操作。例如,使使用者被轉到錯誤的頁面,或使用XMLHttpRequest將會話金鑰的信息發送到外部伺服器,進而洩露登入資訊。
SQL注入則是透過在文本框中插入SQL中常用的「'(結束字串)」、「 -- (註解)」或「;(結束處理)」等,使得原本的SQL執行異常。
例如在這個網站中,有兩個文本框,透過填寫其中一個可以達成SQL注入。
例如在第一個文本框中填入

', (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]」這個用戶的密碼。
執行這樣的操作會得到如下結果。

這裡將密碼存成哈希值,因此一般來說是不會洩漏的,但如果平文的密碼存入資料庫,則可能會被洩漏,進而加入不法存取者的密碼列表(雖然現在Chrome自動生成密碼,但至少在這個網站上使用相同密碼仍是危險的)。
這個詞可能不太為人所熟悉。其實沒有做對策的網站也不少見。
這種攻擊手法是將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>

雖然在表現上看起來並不明顯,但從Form標籤的Hidden中能看出很多不應有的字串。
而點擊後就會導致

發生一鍵送出的情況。
那麼,針對這些問題該如何處理呢,接下來我會基於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")
那麼我們來逐一看看。
這個有對策的網站在顯示時的封包擷取是這樣的。

仔細觀察會發現有一串十六進位的長字串,這是進行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操作都會通過隨機生成的十六進位字串驗證。
因此外部網站很難猜到隨機字串,並且不知道這個參數的存在,這樣請求就會出錯。

res += html.escape(team) + " " + html.escape(str(num)) + "<br>\n"
這裡沒有將字串直接串接,而是使用「html.escape(字串)」進行處理,這樣可以將「<」、「>」等標籤和「"」等字串轉換為能夠正常顯示在瀏覽器的格式。
我們來看看實際結果。

最終的結果會以這樣的格式顯示,而從封包擷取中可以看到

確認是安全的。
這部分簡單點,資料庫相關的庫中都包含有佔位符(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注入,實際上當我們輸入之前的字串時,

會發現打入的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地址清單。

在這裡可以針對被報告的IP地址進行搜索。
這樣就能看到類似於如上的結果。


透過這樣的方式,你可以發現潛在的惡意輸入。此外,若是有用戶使用不正當方式登入,且未執行正常的登出或其他正常手段時,可以懷疑其為會話金鑰劫持行為。
因此,將這樣的日誌保留是非常有用的。
原文出處:https://qiita.com/murasaki1994/items/81fabafaaa1fc0e8fade