來源:infoQ
https://reurl.cc/2b4NGv
Facebook 上有一個名為“Il Programmatore di Merda”(翻譯為“ The Shitty Programmer”,中文含義為“糟糕的程式猿”)的社群, 我經常去浏覽。網站經常分享一些糟糕的代碼和有關程式設計的話題。今天,我看到一段令我難以置信的代碼:

本周最爛代碼
仔細看看,上面的代碼錯誤太多,以至于我不知從何談起。
如果你是一個初級開發工程師,這篇文章會幫你明白上述代碼中存在的一些非常嚴重的問題,并讓你引以為鑒。
128 行錯誤代碼
我把上面的代碼摘錄下來,以便我們進行後面的讨論:
<script>
function authenticateUser(username, password) {
var accounts = apiService.sql(
"SELECT * FROM users"
);
for (var i = 0; i < accounts.length; i++) {
var account = accounts [i];
if (account.username === username &&
account.password === password)
{
return true;
}
}
if ("true" === "true") {
return false;
}
}
$('#login').click(function() {
var username = $("#username").val();
var password = $("#password").val();
var authenticated = authenticateUser(username, password);
if (authenticated === true) {
$.cookie('loggedin', 'yes', { expires: 1 });
} else if (authenticated === false) {
$("error_message").show(LogInFailed);
}
});
</script>
一時之間,我竟不知道從何說起。
上述錯誤大緻分為 3 類:
- 安全問題
- 基本程式設計概念問題
- 代碼格式化問題
2安全問題
我們非常确定以下代碼會在用戶端運作,因為它被包裝在兩個
<script>
标記間(當然,它使用 jQuery 程式設計架構)。不要誤會我的意思,這些代碼即使是運作在伺服器端也很糟糕,在用戶端上運作這些代碼會将你的資料庫暴露給……每個人。
讓我們先看一下
authenticateUser
函數:
function authenticateUser(username, password) {
var accounts = apiService.sql(
"SELECT * FROM users"
);
for (var i = 0; i < accounts.length; i++) {
var account = accounts [i];
if (account.username === username &&
account.password === password)
{
return true;
}
}
if ("true" === "true") {
return false;
}
}
我們的代碼在某些地方有個叫做
apiServices
的接口,它公開了一個
.sql
方法,可以對資料庫進行 SQL 操作。這意味着,如果你在運作上述代碼的浏覽器上打開控制台,就可以執行各種查詢,安全隐患極高。比如,你無需獲得授權就可以這樣做:
apiService.sql("show tables;");
調用上述 API,代碼執行後會傳回資料庫的所有表名稱。
我們暫且假裝這不是一個嚴重的問題。但是請接着往下看:
if (account.username === username &&
account.password === password)
是以,作者的意思是直接儲存了使用者所有的明文密碼,而沒有對它們進行哈希處理?
這簡直不可思議!現在我可以打開 Chrome 浏覽器的調試器,直接檢視每個使用者的明文密碼。
我非常确定,很大一部分使用者會在社交網絡、電子郵件服務、銀行賬戶等服務中使用相同的使用者名和密碼,想象一下,别人可以在沒有任何障礙下就可以拿到你的賬戶和密碼,這得有多可怕。
作者嘗試設定
登入
cookie 的方式也存在問題:
$.cookie('loggedin', 'yes', { expires: 1 });
是以按照代碼的意思,作者使用 jQuery 設定 cookie,讓該 cookie 告知 Web 應用程式使用者是否通過身份驗證。
好吧,千萬不要使用 JavaScript 來設定此類 cookie。
如果你有存儲此類登陸資訊的需求,那麼使用 cookie 确實是最常見的解決方案,這沒有什麼問題!但是使用 JavaScript 設定它們意味着你無法設定
httpOnly
屬性,這會導緻每個惡意腳本都能輕而易舉地通路和擷取你的 cookie 内容。
是的,我知道,他們隻是存儲
'loggedin': 'yes'
的鍵值資訊,可能不是上面我講的那種情況,但總之這是一個糟糕的做法。
另外,打開 Chrome 控制台,我随時可以輸入
$ .cookie('loggedin','yes',{expires: 1000000000000})
指令, 而且即使我沒有使用者帳戶,也會永遠保持登入狀态。
3基本程式設計概念問題
想說的話太多,但無奈時間有限。
很明顯,
authenticateUser
函數寫的就是一堆垃圾,該函數的實作充分表明作者缺乏一些基本的程式設計概念。
function authenticateUser(username, password) {
var accounts = apiService.sql(
"SELECT * FROM users"
);
for (var i = 0; i < accounts.length; i++) {
var account = accounts [i];
if (account.username === username &&
account.password === password)
{
return true;
}
}
if ("true" === "true") {
return false;
}
}
代碼作者為什麼不隻查詢給定使用者名和密碼的使用者,而是檢索出資料庫中的所有使用者呢?如果該資料庫中擁有數百萬個使用者怎麼辦?
還有前面我已經說過了,在這裡我再提一下,為什麼作者不對資料庫中的明文密碼進行哈希處理?
讓我們接着看一下
authenticateUser
函數的傳回值。
我們可以看到,該函數接收兩個 string 類型的參數,最後傳回一個布爾類型的值。是以,下面的代碼即使很糟糕(明文密碼),但也有一定的意義:
for (var i = 0; i < accounts.length; i++) {
var account = accounts [i];
if (account.username === username &&
account.password === password)
{
return true;
}
}
上面代碼的含義很清楚,“是否存在具有 X 使用者名和 Y 密碼的使用者?是的,是以函數執行結果傳回 true”。
但是下面這個代碼:
if ("true" === "true") {
return false;
}
這根本沒有任何道理呀。
為什麼該函數不去掉
always-true
條件判斷,直接傳回 false?
現在,我們繼續接着分析後面的代碼:
$('#login').click(function() {
var username = $("#username").val();
var password = $("#password").val();
var authenticated = authenticateUser(username, password);
if (authenticated === true) {
$.cookie('loggedin', 'yes', { expires: 1 });
} else if (authenticated === false) {
$("error_message").show(LogInFailed);
}
});
使用 jQuery 擷取屬性值的代碼部分沒有什麼問題。問題在于它如何處理
loggedin
使用者的 cookie。
我們之前讨論過這樣一個問題,我可以在我的 Chrome 控制台輸入
$ .cookie('loggedin','yes',{expires:1});
保持認證一整天,甚至都不需要一個帳戶。
是以,這個網站到底是怎麼确定我是誰的?也許它隻是通過使用者名 / 密碼身份驗證顯示一些私人内容,是以它沒有展示任何個人資料。總之,沒有人知道代碼為什麼會這麼寫。
4代碼格式化問題
代碼格式可能是整個代碼中不太重要的部分,但我們可以很容易地判斷出該開發人員複制 / 粘貼了某些網站上的代碼。
下面的代碼片段,我們可以看到開發者使用了雙引号引用字元串:
var username = $("#username").val();
var password = $("#password").val();
然而,下面的代碼卻又使用了單引号字元串:
$.cookie('loggedin', 'yes', { expires: 1 });
這些看起來可能沒有那麼重要,但實際上我們可以确定,開發人員可能已經從 StackOverflow 複制粘貼了一些代碼,甚至都沒有遵循整個代碼庫的代碼規範來重寫它們。當然,這隻是一個小問題,但它表明開發人員并不真正關心和了解代碼的工作方式,隻是希望代碼以某種方式工作。
大家不要誤會,我每天都會在 Google 上進行搜尋,但比起僅僅複制和粘貼代碼來實作功能,了解代碼的工作原理——比如了解如何設定 Cookie,實際上更為重要。如果由于某種原因整個程序中斷了怎麼辦?你如何确定是腳本的哪一部分不起作用呢?
5總結
我絕對可以确定上面的代碼是僞造的。這是我第一次看到使用同步方式進行 SQL 查詢:
var accounts = apiService.sql(
"SELECT * FROM users"
);
通常,我希望查詢功能的實作類似下面這樣:
var accounts = apiService.sql("SELECT * FROM users", (err, res) => {
console.log(err); // some error
console.log(res); // query result
});
或者這樣:
var accounts = await apiService.sql(
"SELECT * FROM users"
);
即使使用同步方式調用
apiService.sql
傳回查詢值(我對此表示懷疑),在内部也必須進行與資料庫的連接配接、執行查詢語句并發送傳回查詢結果,這些過程(你可能已經知道了)明顯是不同步的。
但是,即使上面的代碼不是僞造的,我也可以确信它是由初級開發人員編寫的。我剛剛開始入行寫代碼的一段時間裡,我很确定自己為之前的公司也寫過這麼糟糕的代碼。
這個鍋不能甩給初級開發人員。
讓我們假設上面的代碼是真實的。這裡的初級開發人員正在竭盡所能實作功能。他 / 她尚未開始學習如何正确處理 SQL 查詢、cookie 以及其他需要注意的技術點,這完全可以了解!
進階開發人員應該提供某種形式的指導,以確定初級開發人員可以了解他們的錯誤,保證這樣的錯誤代碼不會在生産環境中使用。
我也可以确認,有些公司其實并不真正在乎開發人員編寫的代碼品質。
代碼能解決問題嗎?——生産環境部署一下就知道了呀。代碼是由初級開發人員編寫的,甚至都沒有進階開發人員的準許嗎?——部署運作一下就知道結果了呀。
哎,Shit happens!
6後記
我在 Reddit 對此進行了一番讨論後,一個非常給力的小夥伴分享了下面的 Reddit 話題:
“This JavaScript code powers a 1,500 user intranet application”
https://www.reddit.com/r/programminghorror/comments/66klvc/this_javascript_code_powers_a_1500_user_intranet
是以,是我錯了。這段代碼并不是僞造的