程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 安全性測驗:測試您的安全性 IQ

安全性測驗:測試您的安全性 IQ

編輯:關於C#

目錄

錯誤 #1(C 或 C++)

錯誤 #2(C 或 C++)

錯誤 #3(可以是任何語言,示例為 C#)

錯誤 #4

錯誤 #5

錯誤 #6 (C#)

錯誤 #7 (C#)

錯誤 #8 (C#)

錯誤 #9 (C#)

錯誤 #10 (Silverlight CLR C#)

我們都喜歡通過復查代碼來檢查安全性錯誤。甚至可以說,我們對此非常擅長。我們並不是在自誇我們是最好的,但我們通常都可以迅速地找出大量錯誤。你能做到嗎?

如果看到一個安全性錯誤,您能否識別出來?通過進行這一測驗來評估一下。每個代碼示例都至少有一個安全漏洞。嘗試找出錯誤並看一看您的得分。在代碼後面是對這些漏洞、注釋的總結,如果條件允許,還會講述安全性開發生命周期 (SDL) 如何幫助查找這些錯誤。感謝 Peter Torr 和 Eric Lippert 提供輸入和代碼示例。

錯誤 #1(C 或 C++)

void func(char *s1, char *s2) {
 char d[32];
 strncpy(d,s1,sizeof d - 1);
 strncat(d,s2,sizeof d - 1);
 ...
}

答案:我們認為應該先來講一講雖然古老但卻非常便於理解的緩沖區溢出。對許多人而言此代碼已足夠安全,因為代碼使用的是受限的 strncpy 和 strncat 函數。但是,只有當緩沖區大小合適時,這些函數才是安全的,而在本例中緩沖區大小是錯誤的。徹頭徹尾的錯誤。

從技術上講,第一個調用是安全的,但第二個調用卻是錯誤的。strncpy 和 strncat 函數的最後一個參數是緩沖區中保留的空間量,而您剛剛通過調用 strncpy 占用了其中的一部分或全部空間。緩沖區溢出。Michael 在 2004 年發表了一篇博客文章,其中講述了與此完全相同的錯誤類型。

在 Visual C++ 2005 及以後的版本中,警告 C4996 是告訴您應將錯誤的函數調用替換為更安全的調用,而 /analyze 選項會發出 C6053 警告,指出 strncat 可能不會以零終止字符串。

老實說,由於種種原因,strncpy 和 strncat(及其 "n" 同類)要比 strcpy 和 strcat(及其同類)更糟糕。首先,返回值有點多余 — 它是指向緩沖區的指針,而緩沖區可能有效,也可能無效。您沒有辦法知道!其次,獲取正確的目標緩沖區大小的確很難。如果您找出了錯誤,可以給自己加一分。

錯誤 #2(C 或 C++)

int main(int argc, char* argv[]) {
 char d[32];
 if (argc==2)
  strcpy(d,argv[1]);
 ...
 return 0;
}

答案:這是一個很棘手的錯誤。過去我們曾多次看到此錯誤被用作緩沖區溢出的示例,而大多數情況下根本無法確定代碼中是否存在安全性錯誤。這完全取決於代碼的使用方式。

如果這是標准的 Win32 EXE,則此處不存在安全性錯誤,因為可能發生的最糟糕後果就是攻擊您自己並自行運行代碼,但這並不是安全性錯誤。

現在,如果此代碼位於以 SYSTEM 權限運行的 ServiceMain Windows 服務中,或者用作 Linux 應用程序 setuid 根的主函數,則此代碼將變為不折不扣的安全性錯誤。

讓我們假設此代碼被用作標記為 setuid 根的 Linux 應用程序。當應用程序由普通用戶啟動時,應用程序實際上以根身份運行,這意味著這是一個本地權限提升漏洞。

如“錯誤 #1”中給出的代碼示例所示,調用 strcpy 時會發出 C4996 警告,/analyze 將發出 C6204,指示存在潛在的緩沖區溢出。如果您回答“喔!我需要更多的上下文”,那麼給自己加兩分;否則不得分。

錯誤 #3(可以是任何語言,示例為 C#)

byte[] GetKey(UInt32 keySize) {
 byte[] key = null;
 try {
  key = new byte[keySize];
  RNGCryptoServiceProvider.Create().GetBytes(key);
 }
 catch (Exception e) {
  Random r = new Random();
  r.NextBytes(key);
 }
 return key;
}

答案:在這個糟糕的密鑰生成代碼中有兩個錯誤。第一個非常明顯:如果在調用從密碼學角度上來說非常強大的隨機數字生成器時失敗,則代碼會捕獲此異常,然後調用根本沒有任何用處而且可預測的隨機生成器。如果您已發現這一點,就給自己加一分。SDL 要求使用在密碼學角度上隨機的數字來生成密鑰。

但還有一個錯誤:代碼會捕獲所有異常。除了極少數情況外,捕獲所有異常(無論是 C++ 異常、Microsoft .NET Framework 異常,還是 Windows 中的結構化異常處理)會掩蓋真正的錯誤。因此不要這樣做。

在使用 /analyze 進行編譯時,C 或 C++ 中捕獲所有異常(包括緩沖區溢出等訪問保護錯誤)的結構化異常處理程序將產生一個 C6320 警告。這種設計使攻擊者能夠反復發起對動畫光標錯誤 MS07-017 的攻擊。如果您找出了異常處理錯誤,就給自己再加一分。

錯誤 #4

void func(const char *s) {
 if (!s) return;
 char t[3];
 memcpy(t,s,3);
 t[3] = 0;
 ...
}

答案:幾年前當 Windows Vista 還處在開發過程中的時候,我們曾在其中發現了一個類似錯誤。但這是安全性錯誤嗎?很明顯,它是編碼錯誤,因為代碼將寫入第四個數組元素,但數組只有三個元素長。請記住,數組是從零開始,而不是一。我堅持認為這不是安全性錯誤,因為攻擊者沒有獲取任何控制權限。

但是,如果該錯誤看上去像下面的示例那樣,攻擊者控制了 i,那麼就意味著攻擊者可以在內存中的任意位置寫入零。這時它就成了真正的安全性錯誤:

void func(const char *s, int i) {
 if (!s) return;
 char t[3];
 memcpy(t,s,3);
 t[i] = 0;
 ...
}

使用 /analyze 進行編譯時,此代碼將生成 C6201“超出有效索引范圍”警告。如果您說“不是安全性錯誤”,就給自己加一分。

錯誤 #5

public class Barrel {
 // By default, a barrel contains one rhesus monkey. 
 private static Monkey[] defaultMonkeys =
  new[] { new RhesusMonkey() };
 // backing store for property.
 private IEnumerable<Monkey> monkeys = null;
 public IEnumerable<Monkey> Monkeys {
  get {
   if (monkeys == null) {
    if (MonkeysReady())
     monkeys = PopulateMonkeys();
    else
     monkeys = defaultMonkeys;
   }
   return monkeys;
  }
 }
}

答案:這是一個難題。此類作者認為它們是安全高效的。其後備存儲是保密的,屬性為只讀,屬性類型為 IEnumerable<T>,因此調用方無法執行任何操作,只能讀取 Barrel 的狀態。

作者忘記了心懷叵測的調用方可能會嘗試將屬性的返回值轉換為 Monkey[]。如果有兩個 Barrel,每個都有默認的 Monkey 列表,那麼擁有其中一個 Barrel 的惡意調用方則可以使用其他任何 Monkey(或 null)來替換靜態默認列表中的 RhesusMonkey,從而實際改變另一個 Barrel 的狀態。

此處的解決方案是緩存 ReadOnlyCollection<T> 或其他某個真正的只讀存儲,以保護底層數組免受調用方惡意或意外的轉換。如果您抓住了這一點,就給自己加兩分。

錯誤 #6 (C#)

protected void Page_Load(object sender, EventArgs e) {
 string lastLogin = Request["LastLogin"];
 if (String.IsNullOrEmpty(lastLogin)) {
  HttpCookie lastLoginCookie = new HttpCookie("LastLogin",
   DateTime.Now.ToShortDateString());
  lastLoginCookie.Expires = DateTime.Now.AddYears(1);
  Response.Cookies.Add(lastLoginCookie);
 }
 else {
  Response.Write("Welcome back! You last logged in on " + lastLogin);
  Response.Cookies["LastLogin"].Value =
   DateTime.Now.ToShortDateString();
 }
}

答案:這是一個非常簡單的、跨站點的腳本編寫漏洞,也是 Web 上最常見的漏洞。盡管代碼似乎暗示 lastLogin 值始終來自 cookie,但實際上 HttpRequest.Item 屬性更傾向於使用來自查詢字符串的值,而不是來自 cookie 的值。

換句話說,無論 lastLogin cookie 被設置為何值,只要攻擊者將成對的名稱/值 lastLogin=<script>alert('0wned!')</script> 添加到查詢字符串中,應用程序就會為 lastLogin 變量的值選擇惡意腳本輸入。如果您的回答是 XSS,則給自己加一分。

錯誤 #7 (C#)

private decimal? lookupPrice(XmlDocument doc) {
 XmlNode node = doc.SelectSingleNode(
  @"//products/product[id/text()='" +
  Request["itemId"] + "']/price");
 if (node == null)
  return null;
 else
  return (Convert.ToDecimal(node.InnerText));
}

答案:如果您說是 XPath 注入,則給自己加一分。XPath 注入的工作原理與其同類但卻更為著名(也可以說是臭名昭著)的 SQL 注入完全一樣。此代碼創建了一個將 XPath 代碼和未經驗證的保留用戶輸入合並在一起的查詢,因此很容易受到注入攻擊。任何應用程序如果它所處理的文本隨後將被用於執行某種形式的操作,則它們都面臨注入攻擊的威脅。

錯誤 #8 (C#)

public class CustomSessionIDManager : System.Web.Session  State.SessionIDManager
{
  private static object lockObject = new object();
  public override string CreateSessionID(HttpContext context)
  {
    lock (lockObject)
    {
      Int32? lastSessionId = (int?)context.Application        ["LastSessionId"];
      if (lastSessionId == null)
        lastSessionId = 1;
      else
        lastSessionId++;
      context.Application["LastSessionId"] = lastSessionId;
      return lastSessionId.ToString();
    }
  }
}

答案:這裡有兩個主要問題。雖然此代碼可以正確地對應用程序邏輯加一道鎖,從而確保兩個線程不會同時創建相同的會話 ID,但它仍然不能在服務器場中進行安全部署。HttpContext.Application 對象所引用的應用程序狀態並不在各個服務器之間共享。如果此應用程序已在服務器場中部署,則可能會導致會話 ID 沖突。如果您捕獲了此錯誤,則給自己加一分。

另一個嚴重的問題在於可以輕松猜出此類生成的會話 ID 是一個連續整數。如果某用戶看到了他的會話令牌並注意到其會話 ID 為 100,則該用戶可以使用簡單的浏覽器實用程序將會話 ID 改為 99 或 98 或其他任何更小的值,從而攔截這些用戶的會話。

在這種情況下,更適合開發人員的方案是使用 GUID 或其他較大的、隨機的字符串組合字母和數字。如果您意識到有序整數對會話 ID 令牌而言是糟糕的選擇,則您獲得一分。

錯誤 #9 (C#)

bool login(string username,
      string password,
      SqlConnection connection,
      out string errorMessage) {
 SqlCommand selectUserAndPassword = new SqlCommand(
  "SELECT Password FROM UserAccount WHERE Username = @username",
  connection);
 selectUserAndPassword.Parameters.Add(
  new SqlParameter("@username", username));
 string validPassword =
  (string)selectUserAndPassword.ExecuteScalar();
 if (validPassword == null) {
  // the user doesn't exist in the database
  errorMessage = "Invalid user name";
  return false;
 }
 else if (validPassword != password) {
  // the given password doesn't match
  errorMessage = "Incorrect password";
  return false;
 }
 else {
  // success
  errorMessage = String.Empty;
  return true;
 }
}

答案:此處最大的問題在於,當登錄失敗時,應用程序向用戶返回的信息過多。雖然對用戶來說,弄清到底是輸錯了密碼還是完全忘記了用戶名無疑很有幫助,但此信息也幫助了那些試圖對應用程序發起強力攻擊的攻擊者。盡管聽起來與常理相悖,但在此情況下還是不要提供幫助。如果登錄失敗則顯示“用戶名或密碼無效”等消息,而不是“用戶名無效”和“密碼無效”。

如果您發現了這一點,則加一分。如果您還記得應用程序不應在數據庫中存儲純文本密碼,再獎勵您一分;在這種情況下應存儲和比較經過處理的密碼哈希值。

那麼該如何評定結果呢?

分數 評論 15+ 我們的理想人選。 11-14 還不錯吧,應該說很不錯。現在將您的才干應用到您的搭檔編寫的所有代碼中。 7-10 嗯。還不錯。您無疑還有一些方面需要學習,但是要比現在編寫 Web 應用程序的其中 50% 的開發人員強一些。 4-6 您有很多方面需要學習。請到您喜歡的書店購買本文兩位作者撰寫的全部書籍。 0-3 退出編輯器和編譯器,以免任何人受到傷害。

錯誤 #10 (Silverlight CLR C#)

bool verifyCode(string discountCode) {
 // We store the hash of the secret code instead of
 // the plaintext of the secret code.
 // Hash the incoming value and compare it against
 // the stored hash.
 SHA1Managed hashFunction = new SHA1Managed();
 byte[] codeHash =
  hashFunction.ComputeHash(
   System.Text.Encoding.Unicode.GetBytes(discountCode));
 byte[] secretCode = new byte[] {
  116, 46, 130, 122, 36, 234, 158, 125, 163, 122,
  157, 186, 64, 142, 51, 153, 113, 79, 1, 42 };
 if (codeHash.Length != secretCode.Length) {
  // The hash lengths don't match, so the strings don't
  // match this should never happen, but we check anyway
  return false;
 }
 // perform an element-by-element comparison of the arrays
 for (int i = 0; i < codeHash.Length; i++) {
  if (codeHash[i] != secretCode[i])
   return false; // the hashes don't match
 }
 // all the elements match, so the strings match
 // the discount code is valid, inform the server
 WebServiceSoapClient client = new WebServiceSoapClient();
 client.ApplyDiscountCode();
 return true;
}

答案:開發人員做出了一個明智決定,不在代碼中嵌入純文本形式的加密代碼。如果您只需測試用戶是否知道機密內容(如折扣代碼或密碼),存儲該機密內容的哈希值並比較哈希值肯定要比存儲純文本並直接比較字符串要好一些。遺憾的是,開發人員選擇了使用 SHA-1 哈希算法,它暴露出了嚴重的問題,之後被 SDL 禁止。更好的選擇是使用 SHA256Managed 類,它可以實現經 SDL 批准和推薦的 SHA-256 哈希算法。如果您發現了這一點,則加一分。

比選擇 SHA-1 而不是 SHA-256 還要糟糕的是開發人員忽略了對哈希值進行處理。未經處理的哈希值非常容易通過預先計算的哈希表(通常稱為彩虹表)破解。攻擊者可能在很短的時間內便可從未經處理的哈希值判斷出原始的純文本加密代碼。(作者將在 SDL 博客中發表賀詞,以感謝第一個使用純文本加密代碼響應我們號召的人。)如果您捕獲了未經處理的哈希值,則給自己加一分。

但是,此代碼存在的最大問題在於它竟然在客戶端計算機上執行!要記得我們開頭所說的,是 Silverlight CLR 代碼在用戶的浏覽器中運行。在客戶端上運行的任何代碼都可能被攻擊者所操控。無需多言。對於鐵了心的用戶,沒有什麼能阻止他在運行 Silverlight 代碼的浏覽器實例中附加調試程序並在代碼執行時單步調試它。

他可以將 codeHash 變量設置為等於 secretCode 哈希值,這樣一來比較邏輯始終都會成功。或者他可以完全略過驗證邏輯,直接跳到應用折扣代碼的 Web 服務調用的當前指令處。最簡單的方式是,他可以完全避開調試程序,只需直接調用 Web 服務方法 ApplyDiscountCode!

必須要知道,即使您可以使用 C# 或 Visual Basic 來創建 Silverlight 應用程序(就像您處理 ASP.NET Web 窗體那樣),Silverlight 代碼也是在客戶端計算機上運行,而 Web 窗體代碼是在服務器上運行。在客戶端上運行的代碼可能會被攻擊者查看和篡改。絕不要將機密內容嵌入到客戶端代碼中,或允許客戶端代碼執行特權決策(諸如折扣代碼是否有效,或是否授權用戶執行某個操作)。如果您捕獲了此錯誤,則給自己加一分。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved