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

          文 / Gwyn Fisher
                對(duì)于所有類型環(huán)境中的開發(fā)人員來說,安全性正成為一個(gè)越來越重要的主題,即便過去一直認(rèn)為安全性不成問題的嵌入式系統(tǒng)也是如此。本文將介紹幾種類型的編碼漏洞,指出漏洞是什么、如何降低代碼被攻擊的風(fēng)險(xiǎn)、如何更好地找出代碼中的此類缺陷。

          注入攻擊
                通過將信息注入正在運(yùn)行的流程,攻擊者可以危害進(jìn)程的運(yùn)行狀態(tài),以反射到開發(fā)人員無法保護(hù)的某種最終目標(biāo)。例如,攻擊者可能會(huì)通過堆棧溢出(stack corruption)將代碼注入進(jìn)程,從而執(zhí)行攻擊者選定的代碼。此外,攻擊者也可能嘗試將數(shù)據(jù)注入數(shù)據(jù)庫(kù),供將來使用;或?qū)⑽词鼙Wo(hù)的字符串注入數(shù)據(jù) 庫(kù)查詢,獲取比開發(fā)人員更多的信息。無論出于怎樣的目的,注入總是一件壞事,總是需要謹(jǐn)慎對(duì)待的。
                最惡劣的注入攻擊形式也許是代碼注入——將新代碼置入正在運(yùn)行的進(jìn)程的內(nèi)存空間,隨后指示正在運(yùn)行的進(jìn)程執(zhí)行這些代碼。此類攻擊如果成功,則幾乎可以進(jìn)行任何操作,因?yàn)檎谶\(yùn)行的進(jìn)程完全被劫持,可執(zhí)行攻擊者希望執(zhí)行的任何代碼。
                此類攻擊最著名的示例之一就是 Windows 動(dòng)畫光標(biāo)攻擊,這正是本文要討論的模式。攻擊者利用一個(gè)簡(jiǎn)單的 Web 頁(yè)面將形式不當(dāng)?shù)膭?dòng)畫光標(biāo)文件下載到查看者的 PC 中,導(dǎo)致瀏覽器調(diào)用此動(dòng)畫光標(biāo),動(dòng)畫光標(biāo)調(diào)用時(shí)可能發(fā)生任意代碼的注入。實(shí)際上,這是一個(gè)完美的攻擊載體:因?yàn)樗灰髮?duì)被攻擊機(jī)器的任何實(shí)際訪問、最終 用戶根本意識(shí)不到任何可能發(fā)生的麻煩;此外,如果攻擊效果的惡意也是適度的,則對(duì)最終用戶的外部影響幾乎是零。
                考慮示例 1(a),當(dāng)然,這改寫自 Windows 攻擊,它構(gòu)成了此類攻擊載體的基礎(chǔ)。這里的開發(fā)人員對(duì)于傳入流的可靠性做出了基本的假設(shè)。信任流和并相信一切都沒問題。使用基于堆棧的將被非串形化 (deserialized)的類型調(diào)用函數(shù),未知數(shù)據(jù)流和代碼注入肯定會(huì)在某個(gè)時(shí)間點(diǎn)出現(xiàn)。
          (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 注入攻擊。
                這是怎樣發(fā)生的?假設(shè)您調(diào)用示例 1(b)中的函數(shù)。我們就得到了一個(gè)易于利用的攻擊載體。這里的問題在于 SOMETYPE 在編譯時(shí)的大小是固定的。假設(shè)此類型在內(nèi)存中使用 128 個(gè)字節(jié)表示。再假設(shè)您構(gòu)建傳入流時(shí),使前 4 個(gè)字節(jié)(要非串形化的內(nèi)容的長(zhǎng)度)的讀數(shù)為 256。現(xiàn)在,您沒有檢查正在處理的內(nèi)容的有效性,而是將 256 個(gè)字節(jié)復(fù)制到了僅為 128 個(gè)字節(jié)的保留堆棧空間內(nèi)。
                考慮到發(fā)布模式堆棧的典型布局,您顯然遇到了麻煩。查看堆棧,了解原因所在。每個(gè)被調(diào)用的函數(shù)都會(huì)將其本地?cái)?shù)據(jù)布設(shè)到堆棧的一個(gè)幀內(nèi),通常是通過在輸入時(shí) 從堆棧指針減去本地?cái)?shù)據(jù)的已知大小(加上處理調(diào)用鏈本身所需的任何管理數(shù)據(jù))實(shí)現(xiàn)的。編譯器發(fā)出的理想函數(shù) prolog(偽代碼)如下所示:
           .foo
           sub sp, 128  ; sizeof SOMETYPE

                隨后,對(duì)可利用函數(shù)的調(diào)用應(yīng)如下所示:
          push sp   ; push the SOMETYPE
            local variable
          push ap   ; push the stream
            pointer (comes from 1st argument)
          call LoadTypeFromStream
          ret
                在調(diào)用 foo() 時(shí),調(diào)用方將流地址以及返回地址(作為使用調(diào)用指令或平臺(tái)上可用的同等部分的隱式效果)壓入堆棧,使堆棧內(nèi)容中有 128 個(gè)字節(jié)是為我們的類型保留的,且緊鄰返回給 foo() 調(diào)用方的返回地址,參見圖 1。
                現(xiàn)在,LoadTypeFromStream 執(zhí)行,并將 256 個(gè)字節(jié)寫入所提供的地址,也就是在我們調(diào)用函數(shù)之前堆棧指針(SP)的值。這會(huì)覆蓋應(yīng)該使用的 128 個(gè)字節(jié)(本例中位于地址 0x1000 處),加上隨后的 128 個(gè)字節(jié),包括傳入的參數(shù)指針、返回地址以及堆棧中隨后 128 個(gè)字節(jié)內(nèi)存儲(chǔ)的其他任何信息。
                那么攻擊者怎樣利用這樣的漏洞呢?并不簡(jiǎn)單,需要經(jīng)過反復(fù)的試錯(cuò)。實(shí)際上,攻擊者要安排攻擊,使覆蓋的返回地址將控制權(quán)移交給攻擊者,而非預(yù)期調(diào)用方函 數(shù)。因而,攻擊者需要準(zhǔn)確了解要利用哪些數(shù)據(jù)結(jié)構(gòu),這樣的數(shù)據(jù)結(jié)構(gòu)在要攻擊的任意版本的操作系統(tǒng)或應(yīng)用程序上有多大、周邊有哪些內(nèi)容(以便正確設(shè)定偽造的 返回地址)、如何有意義地插入足夠的信息以使返回地址和其他效果能夠?qū)崿F(xiàn)某種惡意操作。
                這一切做起來并不簡(jiǎn)單,但多種多樣的攻擊表明,總是有人有太多的空閑時(shí)間。
                應(yīng)如何防范此類攻擊?這是一次攻擊還是多重攻擊?所寫入的代碼是否真的像這里所顯示的這樣笨拙?現(xiàn)代編譯器是否會(huì)對(duì)堆棧幀布局做一些特殊處理,以避免此類問題?
                總而言之,模糊處理就等于沒有防御。我們都認(rèn)識(shí)到,程序員將攻擊預(yù)想得越簡(jiǎn)單,攻擊出現(xiàn)的可能性就越高。然而,即便是復(fù)雜的代碼,若未進(jìn)行合理防御,也遲 早會(huì)受到攻擊。這種利用被污染的數(shù)據(jù)流和非常基本的緩沖溢出漏洞的攻擊,多年以來這一直是熱門的研究課題,但每年仍然會(huì)出現(xiàn)大量此類攻擊。
                防范此類攻擊的效果甚微,因?yàn)楣粜问綇?fù)雜——注意您的數(shù)據(jù)假設(shè)。只要在示例1(a)中添加一行簡(jiǎn)單的代碼,就會(huì)使其更加安全,參見示例1(c)。顯然, 隨著流交互變得更加復(fù)雜,保護(hù)的要求也隨之復(fù)雜化,但基本上說代碼注入是編碼中“不可饒恕”的過失,因?yàn)榉婪端姆椒ㄊ悄菢悠占昂秃?jiǎn)單。

          SQL 注入
                此外還存在其他一些類型的 SQL 注入,可能會(huì)給以數(shù)據(jù)庫(kù)為中心的應(yīng)用程序造成嚴(yán)重的問題。在某些情況下,攻擊者只是嘗試訪問更多的內(nèi)容。在另一些情況下,攻擊者關(guān)注的則是在數(shù)據(jù)庫(kù)中存儲(chǔ) 新信息,以便使應(yīng)用程序此后在不知情的前提下使用此類信息,入侵最終用戶的會(huì)話。
                基于查詢的攻擊關(guān)注的是一種普遍應(yīng)用的反模式,使用字符串串聯(lián)構(gòu)建查詢。這種類型的漏洞常常出現(xiàn)在面向 Web 的應(yīng)用程序中,在所有常用頁(yè)面產(chǎn)品——包括 PHP、ASP、JSP 等及其后備控制器邏輯中同樣常見。
                這種漏洞的核心是開發(fā)人員使用直接查詢執(zhí)行,而非利用查詢準(zhǔn)備來運(yùn)行數(shù)據(jù)庫(kù)交互。考慮以下登錄驗(yàn)證查詢示例:
          SELECT ID FROM USERS WHERE NAME= 'user' AND PWD='password'
                用戶將看到一個(gè)簡(jiǎn)單的 HTML 表單,該表單包含兩個(gè)輸入框并使用了這種反模式。從表單傳入的參數(shù)(無論所討論的頁(yè)面產(chǎn)品是怎樣接收到這些參數(shù)的)都將通過串聯(lián)直接代入查詢的字符串形式。
          考慮攻擊者提供的一組參數(shù):
          NAME:   x
          PWD:    x' OR '1' = '1
                運(yùn)行串聯(lián),結(jié)果將得到被利用的查詢:
          SELECT ID FROM USERS WHERE NAME=
            'x' AND PWD='x' OR '1' = '1'
                如果登錄僅檢查該語(yǔ)句執(zhí)行成功與否(而未考慮結(jié)果行),攻擊者即可迅速獲得該應(yīng)用程序所處理的任意用戶記錄可提供的任意訪問權(quán)限。很多應(yīng)用程序的用戶表的第一行都是為超級(jí)用戶保留的,攻擊此類應(yīng)用程序輕而易舉。
                利用未謹(jǐn)慎處理數(shù)據(jù)庫(kù)語(yǔ)句內(nèi)代入字符串的應(yīng)用程序,攻擊者可實(shí)現(xiàn)多種其他形式的攻擊。這種反模式極為常見(參見最近的 Microsoft 公告和其他內(nèi)容了解其普遍性),緩解方法也同樣簡(jiǎn)單,并可置于基本數(shù)據(jù)庫(kù) API 之中:使用準(zhǔn)備好的語(yǔ)句而非字符串串聯(lián)。
                例如,考慮示例2 中的錯(cuò)誤實(shí)現(xiàn)。此函數(shù)嚴(yán)格遵循反模式,還通過拋出包含傳入(未過濾)數(shù)據(jù)(即用戶名)的異常而執(zhí)行了另外一項(xiàng)重要的 no-no 操作。如果以響應(yīng)的形式為用戶呈現(xiàn)此數(shù)據(jù),您就很可能遇到某些惡意利用,特別是可能遭遇跨站腳本攻擊。
          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 錯(cuò)誤實(shí)現(xiàn)。
                為了修正此代碼,不應(yīng)動(dòng)態(tài)構(gòu)建 SQL 查詢,而是直接構(gòu)建準(zhǔn)備好的語(yǔ)句,并使用它來代替?zhèn)魅雲(yún)?shù)。
          我們將準(zhǔn)備的語(yǔ)句會(huì)為參數(shù)保留空間,并且不易受此類攻擊利用,原因就在于它的詞匯方面并不像字符串串聯(lián)那樣脆弱。
                考慮以下語(yǔ)句(準(zhǔn)備該語(yǔ)句的目的與前面提到的串聯(lián)字符串相同):
          SELECT ID FROM USERS WHERE USER=?
             AND PWD=?
                我使用這個(gè)準(zhǔn)備好的語(yǔ)句代入了 user 和 pwd 參數(shù)的傳入數(shù)據(jù)。如果我們將之前被利用的字符串作為輸入,結(jié)果將是查詢代入過程出錯(cuò),因?yàn)椴荒軐瑔我?hào)等特殊字符的參數(shù)提供給準(zhǔn)備好的查詢。
                其他可能出現(xiàn)的利用也能在不同階段捕捉到,但如示例3 所示,新實(shí)現(xiàn)的創(chuàng)建與原實(shí)現(xiàn)一樣簡(jiǎn)單,但安全性要高得多(我們也從拋出的異常中刪除了用戶名,這樣可以避免在未經(jīng)過濾的情況下將其公開給調(diào)用方的危險(xiǎn))。
          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,在處理來自最終用戶的數(shù)據(jù)時(shí),始終應(yīng)使用準(zhǔn)備好的語(yǔ)句來利用數(shù)據(jù)庫(kù)本身內(nèi)置的過濾和解析功能。

          跨站點(diǎn)腳本攻擊(XSS)
                在早期的瀏覽器版本中,對(duì)于JavaScript 施加的第一項(xiàng)限制就是為頁(yè)面內(nèi)容建立一種邊界,使一個(gè)站點(diǎn)提供的一個(gè)框架內(nèi)執(zhí)行的腳本無法訪問其他站點(diǎn)提供的框架中的內(nèi)容。因而,跨站點(diǎn)腳本攻擊這種攻擊 模式關(guān)注的是使來自一個(gè)站點(diǎn)(攻擊者站點(diǎn))的腳本能夠訪問其他站點(diǎn)的內(nèi)容(例如,用戶的銀行賬戶站點(diǎn))。
                為此,用戶通常必然要訪問一個(gè)惡意或不可信的網(wǎng)站,而社會(huì)工程的眾多試驗(yàn)已經(jīng)顯示,用戶可能會(huì)被最古怪的站點(diǎn)吸引。
                在此類漏洞中,最常見的形式就是簡(jiǎn)單的反射漏洞,在一次服務(wù)器請(qǐng)求中將未經(jīng)過濾的 HTML 參數(shù)(通常是表單參數(shù))反射給用戶。這種攻擊載體的標(biāo)準(zhǔn)形式首先是通過搜索引擎結(jié)果頁(yè)面顯示出來的,通常會(huì)在頁(yè)面標(biāo)題中反射用戶的查詢關(guān)鍵詞。如果未經(jīng)過 濾,這種反射回的查詢關(guān)鍵詞很可能包含一些編碼不當(dāng)?shù)?HTML 標(biāo)記,但可被接收方瀏覽器解釋為有效的 HTML。
                實(shí)際上,未經(jīng)過濾的傳入數(shù)據(jù)的任何反射都會(huì)造成問題,因?yàn)?XSS數(shù)量和種類始終在增加,參見示例4。
          public void doGet(HttpServletRequest req, HttpServletResponse res)
          {
              string title = req.getParameter("searchTerm");
              res.getOutputStream().write(title.getBytes("UTF-8"));
          }
          示例4 未經(jīng)過濾的傳入數(shù)據(jù)本身就存在問題。
                XSS反射的表現(xiàn)十分簡(jiǎn)單,而解決此問題的方法也極為簡(jiǎn)單——將從傳入請(qǐng)求中讀取的一切內(nèi)容編碼,之后再回發(fā)給瀏覽器即可。盡管我們?cè)谶@里的示例中使用了 Java,但包括HTML編碼機(jī)制的所有常見頁(yè)面產(chǎn)品均可用以避免此類漏洞。例如,下面這條 ASP 語(yǔ)句就可能被利用:
          Response.Write Request.Form("username""
                反之,以下語(yǔ)句則不能被利用:
          Response.Write Server.HTMLEncode( Request.Form("username"))
                盡管仍然沒有內(nèi)置對(duì)象可用于執(zhí)行標(biāo)準(zhǔn)轉(zhuǎn)換,但也可在 Java 中進(jìn)行類似轉(zhuǎn)換,以避免此類利用。也就是說,可輕松編寫一個(gè)類似的 String 轉(zhuǎn)換程序。對(duì)于尋找“現(xiàn)成”產(chǎn)品包的用戶,JTidy 項(xiàng)目(jtidy.sourceforge.net)是一個(gè)理想的起點(diǎn)。
                其他更加復(fù)雜的 XSS 表現(xiàn)形式以未過濾用戶輸入的持久存儲(chǔ)為中心,此類輸入內(nèi)容會(huì)在隨后用于提供響應(yīng)內(nèi)容。這是一類更難以診斷的 XSS,因?yàn)楣裟J讲粌H依賴于所存儲(chǔ)的未經(jīng)過濾的用戶輸入,還依賴于此后對(duì)其他用戶可用的存儲(chǔ)數(shù)據(jù)。
                在早期Web發(fā)展階段,不可信任的論壇提供的軟件包特別易受此類攻擊模式的影響。即便是現(xiàn)在,在數(shù)據(jù)庫(kù)(或文件)中存儲(chǔ)未經(jīng)過濾的傳入數(shù)據(jù)并隨后將所存儲(chǔ)的數(shù)據(jù)發(fā)送給用戶的應(yīng)用程序也易于受到此類持久形式的 XSS 的攻擊。
                同樣,解決方法非常簡(jiǎn)單,只需通過編程,在存儲(chǔ)信息之前將信息編碼或在將信息從持久存儲(chǔ)發(fā)送給用戶之前編碼即可。總而言之,在存儲(chǔ)之前編碼數(shù)據(jù)總是更加安全,這種方式可以保證未來對(duì)此類數(shù)據(jù)的使用免遭XSS 攻擊。

          查找漏洞
                本文介紹的問題的規(guī)避方法易于實(shí)現(xiàn),但對(duì)于嘗試控制現(xiàn)有代碼庫(kù)或新建代碼庫(kù)的安全性的開發(fā)人員或開發(fā)組織而言,所面臨的最大挑戰(zhàn)就是找到漏洞所在。毫無疑 問,可以利用手動(dòng)代碼檢查的方法,但我可以確定地說,圍坐在桌邊、查看大量代碼并嘗試找出可能成為漏洞的內(nèi)容絕非樂事。
                靜態(tài)源代碼分析為此類問題提供了一種可行的解決方案,這種方法關(guān)注代碼中現(xiàn)有的潛在漏洞或弱點(diǎn),而不是像傳統(tǒng)安全性應(yīng)用程序或滲透測(cè)試工具那樣嘗試找到現(xiàn)有漏洞或攻擊載體 。利用 SCA 工具可顯著減少查找并緩解此類問題所需的時(shí)間和工作量。
                目前有多種開源和商業(yè)工具可用,分別具有不同的功能。Klocwork(我目前效力的企業(yè))就提供了這樣一種商業(yè)靜態(tài)源代碼分析產(chǎn)品套件,主要關(guān)注 C、C++ 和 Java,為開發(fā)人員提供了快速、準(zhǔn)確的運(yùn)行缺陷和安全漏洞分析,并且能夠集成在您所選擇的 IDE 之中。■
          原文鏈接:http://www.ddj.com/cpp/210602504
          作者簡(jiǎn)介:
          Gwyn是Klocwork的首席技術(shù)官,他有著20多年的全球技術(shù)經(jīng)驗(yàn)。在Klocwork,Gwyn從事的工作仍然圍繞他最初的愛好——編譯器理論,他致力于將靜態(tài)源代碼分析推向更高級(jí)別。



          posted on 2009-04-10 23:53 .VwV. 閱讀(249) 評(píng)論(0)  編輯  收藏 所屬分類: java

          <2025年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          導(dǎo)航

          統(tǒng)計(jì)

          常用鏈接

          留言簿

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 克拉玛依市| 湾仔区| 祁连县| 珲春市| 潜江市| 陵水| 玛多县| 大新县| 土默特左旗| 昌乐县| 衡山县| 普兰县| 孝感市| 湘潭县| 云安县| 乐都县| 佛冈县| 盐津县| 田东县| 峨边| 荆门市| 互助| 射阳县| 嘉黎县| 项城市| 柘荣县| 山阳县| 竹山县| 桃园县| 即墨市| 和静县| 卫辉市| 正宁县| 永安市| 大竹县| 中卫市| 兴和县| 大丰市| 长兴县| 枣庄市| 铅山县|