C、C++ 和 Java安全編碼實踐提示與技巧

來源:互聯網
上載者:User
 對於所有類型環境中的開發人員來說,安全性正成為一個越來越重要的主題,即便過去一直認為安全性不成問題的嵌入式系統也是如此。本文將介紹幾種類型的編碼漏洞,指出漏洞是什麼、如何降低代碼被攻擊的風險、如何更好地找出代碼中的此類缺陷。

注入攻擊
      通過將資訊注入正在啟動並執行流程,攻擊者可以危害進程的運行狀態,以反射到開發人員無法保護的某種最終目標。例如,攻擊者可能會通過堆疊溢位(stack corruption)將代碼注入進程,從而執行攻擊者選定的代碼。此外,攻擊者也可能嘗試將資料注入資料庫,供將來使用;或將未受保護的字串注入資料庫查詢,擷取比開發人員更多的資訊。無論出於怎樣的目的,注入總是一件壞事,總是需要謹慎對待的。
      最惡劣的注入攻擊形式也許是代碼注入——將新代碼置入正在啟動並執行進程的記憶體空間,隨後指示正在啟動並執行進程執行這些代碼。此類攻擊如果成功,則幾乎可以進行任何操作,因為正在啟動並執行進程完全被劫持,可執行攻擊者希望執行的任何代碼。
      此類攻擊最著名的樣本之一就是 Windows 動畫游標攻擊,這正是本文要討論的模式。攻擊者利用一個簡單的 Web 頁面將形式不當的動畫游標檔案下載到查看者的 PC 中,導致瀏覽器調用此動畫游標,動畫游標調用時可能發生任意代碼的注入。實際上,這是一個完美的攻擊載體:因為它不要求對被攻擊機器的任何實際訪問、終端使用者根本意識不到任何可能發生的麻煩;此外,如果攻擊效果的惡意也是適度的,則對終端使用者的外部影響幾乎是零。
      考慮樣本 1(a),當然,這改寫自 Windows 攻擊,它構成了此類攻擊載體的基礎。這裡的開發人員對於傳入流的可靠性做出了基本的假設。信任流和並相信一切都沒問題。使用基於堆棧的將被非串形化(deserialized)的類型調用函數,未知資料流和代碼注入肯定會在某個時間點出現。(a)
void LoadTypeFromStream(unsigned char* stream, SOMETYPE* typtr)
{
  int len;
  // Get the size of our type's serialized form
  memcpy(&len, stream, sizeof(int));
  // De-serialize the type
  memcpy(typtr, stream + sizeof(int), len);
}(b)void foo(unsigned char* stream)
{
  SOMETYPE ty;
  LoadTypeFromStream(stream, &ty);
}(c)
void LoadTypeFromStream
      (unsigned char* stream, SOMETYPE* typtr)
{
    int len;
    // Get the size of our type's serialized form
    memcpy(&len, stream, sizeof(int));
    // GUARD
    if( len < 0 || len > sizeof(SOMETYPE) )
        throw TaintedDataException();
    // De-serialize the type
    memcpy(typtr, stream + sizeof(int), len);
}樣本1 注入攻擊。
      這是怎樣發生的?假設您調用樣本 1(b)中的函數。我們就得到了一個易於利用的攻擊載體。這裡的問題在於 SOMETYPE 在編譯時間的大小是固定的。假設此類型在記憶體中使用 128 個位元組表示。再假設您構建傳入流時,使前 4 個位元組(要非串形化的內容的長度)的讀數為 256。現在,您沒有檢查正在處理的內容的有效性,而是將 256 個位元組複製到了僅為 128 個位元組的保留堆棧空間內。
      考慮到發布模式堆棧的典型布局,您顯然遇到了麻煩。查看堆棧,瞭解原因所在。每個被調用的函數都會將其本機資料布設到堆棧的一個幀內,通常是通過在輸入時從堆棧指標減去本機資料的已知大小(加上處理調用鏈本身所需的任何管理資料)實現的。編譯器發出的理想函數 prolog(虛擬碼)如下所示:  .foo
 sub sp, 128  ; sizeof SOMETYPE      隨後,對可利用函數的調用應如下所示:push sp   ; push the SOMETYPE
  local variable
push ap   ; push the stream
  pointer (comes from 1st argument)
call LoadTypeFromStream
ret      在調用 foo() 時,調用方將流地址以及返回地址(作為使用調用指令或平台上可用的同等部分的隱式效果)壓入堆棧,使堆棧內容中有 128 個位元組是為我們的類型保留的,且緊鄰返回給 foo() 調用方的返回地址,參見圖 1。
      現在,LoadTypeFromStream 執行,並將 256 個位元組寫入所提供的地址,也就是在我們調用函數之前堆棧指標(SP)的值。這會覆蓋應該使用的 128 個位元組(本例中位於地址 0x1000 處),加上隨後的 128 個位元組,包括傳入的參數指標、返回地址以及堆棧中隨後 128 個位元組記憶體儲的其他任何資訊。
      那麼攻擊者怎樣利用這樣的漏洞呢?並不簡單,需要經過反覆的試錯。實際上,攻擊者要安排攻擊,使覆蓋的返回地址將控制權移交給攻擊者,而非預期調用方函數。因而,攻擊者需要準確瞭解要利用哪些資料結構,這樣的資料結構在要攻擊的任意版本的作業系統或應用程式上有多大、周邊有哪些內容(以便正確設定偽造的返回地址)、如何有意義地插入足夠的資訊以使返回地址和其他效果能夠實現某種惡意操作。
      這一切做起來並不簡單,但多種多樣的攻擊表明,總是有人有太多的空閑時間。
      應如何防範此類攻擊?這是一次攻擊還是多重攻擊?所寫入的代碼是否真的像這裡所顯示的這樣笨拙?現代編譯器是否會對堆疊框架布局做一些特殊處理,以避免此類問題?
      總而言之,模糊處理就等於沒有防禦。我們都認識到,程式員將攻擊預想得越簡單,攻擊出現的可能性就越高。然而,即便是複雜的代碼,若未進行合理防禦,也遲早會受到攻擊。這種利用被汙染的資料流和非常基本的緩衝溢出漏洞的攻擊,多年以來這一直是熱門的研究課題,但每年仍然會出現大量此類攻擊。
      防範此類攻擊的效果甚微,因為攻擊形式複雜——注意您的資料假設。只要在樣本1(a)中添加一行簡單的代碼,就會使其更加安全,參見樣本1(c)。顯然,隨著流互動變得更加複雜,保護的要求也隨之複雜化,但基本上說代碼注入是編碼中“不可饒恕”的過失,因為防範它的方法是那樣普及和簡單。 SQL 注入
      此外還存在其他一些類型的 SQL 注入,可能會給以資料庫為中心的應用程式造成嚴重的問題。在某些情況下,攻擊者只是嘗試訪問更多的內容。在另一些情況下,攻擊者關注的則是在資料庫中儲存新資訊,以便使應用程式此後在不知情的前提下使用此類資訊,入侵終端使用者的會話。
      基於查詢的攻擊關注的是一種普遍應用的反模式,使用字串串聯構建查詢。這種類型的漏洞常常出現在面向 Web 的應用程式中,在所有常用頁面產品——包括 PHP、ASP、JSP 等及其後備控制器邏輯中同樣常見。
      這種漏洞的核心是開發人員使用直接查詢執行,而非利用查詢準備來運行資料庫互動。考慮以下登入驗證查詢樣本:SELECT ID FROM USERS WHERE NAME= 'user' AND PWD='password'      使用者將看到一個簡單的 HTML 表單,該表單包含兩個輸入框並使用了這種反模式。從表單傳入的參數(無論所討論的頁面產品是怎樣接收到這些參數的)都將通過串聯直接代入查詢的字串形式。
考慮攻擊者提供的一組參數:NAME:   x
PWD:    x' OR '1' = '1      運行串聯,結果將得到被利用的查詢:SELECT ID FROM USERS WHERE NAME=
  'x' AND PWD='x' OR '1' = '1'      如果登入僅檢查該語句執行成功與否(而未考慮結果行),攻擊者即可迅速獲得該應用程式所處理的任意使用者記錄可提供的任意存取權限。很多應用程式的使用者表的第一行都是為超級使用者保留的,攻擊此類應用程式輕而易舉。
      利用未謹慎處理資料庫語句內代入字串的應用程式,攻擊者可實現多種其他形式的攻擊。這種反模式極為常見(參見最近的 Microsoft 公告和其他內容瞭解其普遍性),緩解方法也同樣簡單,並可置於基本資料庫 API 之中:使用準備好的語句而非字串串聯。
      例如,考慮樣本2 中的錯誤實現。此函數嚴格遵循反模式,還通過拋出包含傳入(未過濾)資料(即使用者名稱)的異常而執行了另外一項重要的 no-no 操作。如果以響應的形式為使用者呈現此資料,您就很可能遇到某些惡意利用,特別是可能遭遇跨站指令碼攻擊。public void validateUser(String user, String pwd, Connection db)
    throws InvalidUserException
{
  Statement stmt = null;
  ResultSet rs = null;
  try
  {
      // Create the statement
      stmt = db.createStatement();
      String sql = "select id from users where user='" + user +
                   "' and pwd='" + pwd + "'";
      // Execute it, process the result
      rs = stmt.executeQuery(sql);
      if( rs == null || rs.next() == null )
          throw new InvalidUserException(user);
  }
  catch( SQLException e )
  {
      throw new InvalidUserException(user);
  }
  finally
  {
    try { if( rs != null ) rs.close(); } catch( Exception e ) { }
    try { if( stmt != null ) stmt.close(); } catch( Exception e ) { }
  }
}樣本2 錯誤實現。
      為了修正此代碼,不應動態構建 SQL 查詢,而是直接構建準備好的語句,並使用它來代替傳入參數。
我們將準備的語句會為參數保留空間,並且不易受此類攻擊利用,原因就在於它的詞彙方面並不像字串串聯那樣脆弱。
      考慮以下語句(準備該語句的目的與前面提到的串聯字串相同):SELECT ID FROM USERS WHERE USER=?
   AND PWD=?      我使用這個準備好的語句代入了 user 和 pwd 參數的傳入資料。如果我們將之前被利用的字串作為輸入,結果將是查詢代入過程出錯,因為不能將包含單引號等特殊字元的參數提供給準備好的查詢。
      其他可能出現的利用也能在不同階段捕捉到,但如樣本3 所示,新實現的建立與原實現一樣簡單,但安全性要高得多(我們也從拋出的異常中刪除了使用者名稱,這樣可以避免在未經過濾的情況下將其公開給調用方的危險)。public void validateUser(String user, String pwd, Connection db)
    throws InvalidUserException
{
  PreparedStatement stmt = null;
  ResultSet rs = null;  try
  {
      // Prepare the statement, rather than concatenating it
      String sql = "select id from users where user=? and pwd=?");
      stmt = db.prepareStatement(sql);      // Substitute our incoming parameters into the query
      stmt.setString(1, user);
      stmt.setString(2, pwd);      // Execute the query and process the results as before
      rs = stmt.executeQuery();
      if( rs == null || rs.next() == null )
          throw new InvalidUserException();
  }
  catch( SQLException e )
  {
      throw new InvalidUserException();
  }
  finally
  {
    try { if( rs != null ) rs.close(); } catch( Exception e ) { }
    try { if( stmt != null ) stmt.close(); } catch( Exception e ) { }
  }
}樣本3  樣本2 的較為安全的版本。
      總體而言,無論是處理查詢還是DML,在處理來自終端使用者的資料時,始終應使用準備好的語句來利用資料庫本身內建的過濾和解析功能。 跨網站指令碼攻擊(XSS)
      在早期的瀏覽器版本中,對於JavaScript 施加的第一項限制就是為頁面內容建立一種邊界,使一個網站提供的一個架構內執行的指令碼無法訪問其他網站提供的架構中的內容。因而,跨網站指令碼攻擊這種攻擊模式關注的是使來自一個網站(攻擊者網站)的指令碼能夠訪問其他網站的內容(例如,使用者的銀行賬戶網站)。
      為此,使用者通常必然要訪問一個惡意或不可信的網站,而社交工程的眾多實驗已經顯示,使用者可能會被最古怪的網站吸引。
      在此類漏洞中,最常見的形式就是簡單的反射漏洞,在一次伺服器請求中將未經過濾的 HTML 參數(通常是表單參數)反射給使用者。這種攻擊載體的標準形式首先是通過搜尋引擎結果頁面顯示出來的,通常會在頁面標題中反射使用者的查詢關鍵詞。如果未經過濾,這種反射回的查詢關鍵詞很可能包含一些編碼不當的 HTML 標籤,但可被接收方瀏覽器解釋為有效 HTML。
      實際上,未經過濾的傳入資料的任何反射都會造成問題,因為 XSS數量和種類始終在增加,參見樣本4。public void doGet(HttpServletRequest req, HttpServletResponse res)
{
    string title = req.getParameter("searchTerm");
    res.getOutputStream().write(title.getBytes("UTF-8"));
}樣本4 未經過濾的傳入資料本身就存在問題。
      XSS反射的表現十分簡單,而解決此問題的方法也極為簡單——將從傳入請求中讀取的一切內容編碼,之後再回傳給瀏覽器即可。儘管我們在這裡的樣本中使用了Java,但包括HTML編碼機制的所有常見頁面產品均可用以避免此類漏洞。例如,下面這條 ASP 語句就可能被利用:Response.Write Request.Form("username""      反之,以下語句則不能被利用:Response.Write Server.HTMLEncode( Request.Form("username"))
      儘管仍然沒有內建對象可用於執行標準轉換,但也可在 Java 中進行類似轉換,以避免此類利用。也就是說,可輕鬆編寫一個類似的 String 轉換程式。對於尋找“現成”產品包的使用者,JTidy 項目(jtidy.sourceforge.net)是一個理想的起點。
      其他更加複雜的 XSS 表現形式以未過濾使用者輸入的持久儲存為中心,此類輸入內容會在隨後用於提供響應內容。這是一類更難以診斷的 XSS,因為攻擊模式不僅依賴於所儲存的未經過濾的使用者輸入,還依賴於此後對其他使用者可用的儲存資料。
      在早期Web發展階段,不可信任的論壇提供的軟體包特別易受此類攻擊模式的影響。即便是現在,在資料庫(或檔案)中儲存未經過濾的傳入資料並隨後將所儲存的資料發送給使用者的應用程式也易於受到此類持久形式的 XSS 的攻擊。
      同樣,解決方案非常簡單,只需通過編程,在儲存資訊之前將資訊編碼或在將資訊從持久儲存發送給使用者之前編碼即可。總而言之,在儲存之前編碼資料總是更加安全,這種方式可以保證未來對此類資料的使用免遭XSS 攻擊。 尋找漏洞
      本文介紹的問題的規避方法易於實現,但對於嘗試控制現有程式碼程式庫或建立程式碼程式庫的安全性的開發人員或開發組織而言,所面臨的最大挑戰就是找到漏洞所在。毫無疑問,可以利用手動代碼檢查的方法,但我可以確定地說,圍坐在桌邊、查看大量代碼並嘗試找出可能成為漏洞的內容絕非樂事。
      靜態原始碼分析為此類問題提供了一種可行的解決方案,這種方法關注代碼中現有的潛在漏洞或弱點,而不是像傳統安全性應用程式或滲透測試工具那樣嘗試找到現有漏洞或攻擊載體 。利用 SCA 工具可顯著減少尋找並緩解此類問題所需的時間和工作量。
      目前有多種開源和商業工具可用,分別具有不同的功能。Klocwork(我目前效力的企業)就提供了這樣一種商業靜態原始碼分析產品套件,主要關注 C、C++ 和 Java,為開發人員提供了快速、準確的運行缺陷和安全性漏洞分析,並且能夠整合在您所選擇的 IDE 之中。■
原文連結:http://www.ddj.com/cpp/210602504作者簡介:Gwyn是Klocwork的首席技術官,他有著20多年的全球技術經驗。在Klocwork,Gwyn從事的工作仍然圍繞他最初的愛好——編譯器理論,他致力於將靜態原始碼分析推向更進階別。(本文來自《程式員》0902期)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.