安卓應用安全指南4.8輸出到LogCat

apachecn_飛龍發表於2018-03-24

4.8 輸出到 LogCat

原書:Android Application Secure Design/Secure Coding Guidebook

譯者:飛龍

協議:CC BY-NC-SA 4.0

在 Android 中有一種名為 LogCat 的日誌機制,不僅系統日誌資訊,還有應用日誌資訊也會輸出到 LogCat。 LogCat 中的日誌資訊可以從同一裝置中的其他應用中讀出 [17],因此向L ogcat 輸出敏感資訊的應用,被認為具有資訊洩露的漏洞。 敏感資訊不應輸出到 LogCat。

[17] 輸出到 LogCat 的日誌資訊,可以由宣告READ_LOGS許可權的應用讀取。 但是,在 Android 4.1 及更高版本中,無法讀取其他應用輸出的日誌資訊。 但智慧手機使用者可以通過 ADB ,閱讀輸出到 logcat 的每個日誌資訊。

從安全形度來看,在發行版應用中,最好不要輸出任何日誌。 但是,即使在發行版應用的情況下,在某些情況下也會出於某種原因輸出日誌。 在本章中,我們將介紹一些方法,以安全的方式將訊息輸出到 LogCat,即使在發行版應用中也是如此。 除此解釋外,請參考“4.8.3.1 發行版應用中日誌輸出的兩種思路”。

4.8.1 示例程式碼

接下來是在發行版應用中,通過 ProGuard 控制輸出到 LogCat 的日誌的方法。 ProGuard 是自動刪除不需要的程式碼(如未使用的方法等)的優化工具之一。

android.util.Log類有五種型別的日誌輸出方法:Log.e(), Log.w(), Log.i(), Log.d(), Log.v()。對於日誌資訊,有意輸出的日誌資訊(以下稱為“操作日誌資訊”)應該區分於不適合發行版應用的資訊(以下稱為開發日誌資訊),例如除錯日誌。建議使用Log.e()/w()/i()輸出操作日誌資訊,並使用Log.d()/v()輸出開發日誌。正確使用五種日誌輸出方法的詳細資訊,請參閱“4.8.3.2 日誌級別和日誌輸出方法的選擇標準”,另外請參考“4.8.3.3 除錯日誌和VERBOSE日誌並不總是自動刪除”。

這是一個以安全方式使用 LogCat 的例子。此示例包括用於輸出除錯日誌的Log.d()Log.v()。如果應用用於釋出,這兩種方法將被自動刪除。在此示例程式碼中,ProGuard 用於自動刪除呼叫Log.d()/v()的程式碼塊。

要點:

1) 敏感資訊不能由Log.e()/w()/i()System.out / err輸出。

2) 敏感資訊應由Log.d()/v()在需要時輸出。

3) 不應使用Log.d()/v()的返回值(以替換或比較為目的)。

4) 當你構建應用來發布時,你應該在程式碼中引入機制,自動刪除不合適的日誌記錄方法(如Log.d()Log.v())。

5) 必須使用發行版構建配置來建立用於(釋出)發行的 APK 檔案。

ProGuardActivity.java

package org.jssec.android.log.proguard;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class ProGuardActivity extends Activity {

    final static String LOG_TAG = "ProGuardActivity";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_proguard);
        // *** POINT 1 *** Sensitive information must not be output by Log.e()/w()/i(), System.out/err.
        Log.e(LOG_TAG, "Not sensitive information (ERROR)");
        Log.w(LOG_TAG, "Not sensitive information (WARN)");
        Log.i(LOG_TAG, "Not sensitive information (INFO)");
        // *** POINT 2 *** Sensitive information should be output by Log.d()/v() in case of need.
        // *** POINT 3 *** The return value of Log.d()/v()should not be used (with the purpose of substitution or comparison).
        Log.d(LOG_TAG, "sensitive information (DEBUG)");
        Log.v(LOG_TAG, "sensitive information (VERBOSE)");
    }
}

proguard-project.txt

# prevent from changing class name and method name etc.
-dontobfuscate
# *** POINT 4 *** In release build, the build configurations in which Log.d()/v() are deleted automatica
lly should be constructed.
-assumenosideeffects class android.util.Log {
    public static int d(...);
    public static int v(...);
}

要點 5:必須使用發行版構建配置來建立用於(釋出)發行的 APK 檔案。

開發版應用(除錯版本)和發行版應用(釋出版本)之間的LogCat 輸出差異如下圖 4.8-2 所示。

4.8.2 規則書

輸出訊息記錄時,遵循以下規則:

4.8.2.1 操作日誌資訊中不能包含敏感資訊(必需)

輸出到 LogCat 的日誌可以從其他應用中讀取,因此敏感資訊(如使用者的登入資訊)不應該由發行版應用輸出。 在開發過程中,不必編寫輸出敏感資訊的程式碼,或者在釋出之前需要刪除所有這些程式碼。

為了遵循這個規則,首先,不要在操作日誌資訊中包含敏感資訊。 此外,建議構建系統,在構建發行版時,刪除輸出敏感資訊的程式碼。 請參閱“4.8.2.2 構建生成系統,在構建發行版時,自動刪除輸出開發日誌資訊的程式碼(推薦)”。

4.8.2.2 構建生成系統,在構建發行版時,自動刪除輸出開發日誌資訊的程式碼(推薦)

開發應用時,有時最好將敏感資訊輸出到日誌中,來檢查過程內容和除錯,例如複雜邏輯過程中的臨時操作結果,程式內部狀態資訊,通訊協議的資料結構。在開發過程中,將敏感資訊作為除錯日誌輸出並不重要,在這種情況下,相應的日誌輸出程式碼應該在釋出之前刪除,如“4.8.2.1 操作日誌資訊中不能包含敏感資訊(必需)”所述。

為了在構建發行版時,確實刪除了輸出開發日誌資訊的程式碼,應該構建系統,使用某些工具自動執行程式碼刪除。 “4.8.1 示例程式碼”中介紹的 ProGuard 可以用於此方法。如下所述,用 ProGuard 刪除程式碼有一些值得注意的地方。這裡應該將系統用於一些應用,它通過Log.d()/ v()輸出開發日誌資訊,根據“4.8.3.2 日誌級別和日誌輸出方法的選擇標準”。

ProGuard 會自動刪除不需要的程式碼,如未使用的方法。通過指定Log.d()/ v()作為-assumenosideeffects選項的引數,Log.d(),Log.v()`的呼叫被視為不必要的程式碼,並且這些程式碼將被刪除。

-assumenosideeffects class android.util.Log {
    public static int d(...);
    public static int v(...);
}

如果使用這個自動刪除系統,請注意Log.d()Log.v()程式碼在使用其返回值時不會被刪除,因此不應該使用Log.d()Log.v()的返回值。 例如,下一個程式碼中的Log.v()不會被刪除。

int i = android.util.Log.v("tag", "message");
System.out.println(String.format("Log.v() returned %d. ", i)); //Use the returned value of Log.v() for examination

如果你想重複使用原始碼,則應保持專案環境的一致性,包括 ProGuard 設定。 例如,預設Log.d()Log.v()的原始碼將被上面的 ProGuard 設定自動刪除。 如果在未設定 ProGuard 的其他專案中使用此原始碼,則不會刪除Log.d()Log.v(),因此可能會洩露敏感資訊。 重用原始碼時,應確保包括 ProGuard 設定在內的專案環境的一致性。

4.8.2.3 輸出Throwable物件時,使用Log.d()/v()(推薦)

如“4.8.1 示例程式碼”和“4.8.3.2 日誌級別和日誌輸出方法的選擇標準”中所述,輸出敏感資訊不應通過Log.e()/w()/i()輸出來記錄。 另一方面,為了使開發者輸出程式異常的細節來記錄,當異常發生時,在某些情況下,堆疊蹤跡通過Log.e(..., Throwable tr)/w(..., Throwable tr)/i(..., Throwable
tr)
輸出到 LogCat。 但是,敏感資訊有時可能包含在堆疊蹤跡中,因為它顯示程式的詳細內部結構。 例如,當SQLiteException按原樣輸出時,會輸出 SQL 語句的型別,因此可能會提供 SQL 注入攻擊的線索。 因此,建議在輸出Throwable物件時,僅使用Log.d()/v()方法。

4.8.2.4 僅僅將android.util.Log類的方法用於日誌輸出(推薦)

在開發過程中,你可以通過System.out / err輸出日誌,來驗證應用的行為是否按預期工作。 當然,日誌可以通過System.out / errprint()/ println()方法輸出到 LogCat,但強烈建議僅使用android.util.Log類的方法,原因如下。

在輸出日誌時,一般根據資訊的緊急程度,正確使用最合適的輸出方法,並控制輸出。 例如,使用嚴重錯誤,注意,簡單應用的資訊通知等類別。 然而,在這種情況下,在釋出時需要輸出的資訊(操作日誌資訊),和可以包括敏感資訊(開發日誌資訊)的資訊,通過相同的方法輸出。 所以,當刪除輸出敏感資訊的程式碼時,可能會存在一些刪除操作被忽略掉的危險。

除此之外,當使用android.util.LogSystem.out / err進行日誌輸出時,與僅使用android.util.Log相比,需要考慮的因素會增加,因此可能會出現一些錯誤,比如 一些刪除被忽略掉了。

為了減少上述錯誤發生的風險,建議僅使用android.util.Log類的方法。

4.8.3 高階話題

4.8.3.1 釋出版應用中日誌輸出的兩種思路

釋出版應用中有兩種思考日誌輸出的方式。一個是任何日誌都不應該輸出,另一個是用於以後分析的必要資訊應該作為日誌輸出。從安全形度來看,最好是,任何日誌都不應該在發行版應用中輸出,但有時候,即使在發行版本應用中,出於各種原因也會輸出日誌。每種思考方式按照以下描述。

前者是“任何日誌都不應該輸出”,這是因為,在發行版應用中輸出日誌沒有那麼重要,並且存在洩露敏感資訊的風險。這是因為開發人員沒有辦法在 Android 應用執行環境中收集發行版應用的日誌資訊,這與許多 Web 應用的執行環境不同。基於這種思想,日誌程式碼僅用於開發階段,並且在構建發行版應用時刪除所有日誌程式碼。

後者是“必要的資訊應作為日誌輸出,以供日後分析”,作為客戶支援中,分析應用錯誤的最終選項,以防你的客戶支援有任何疑問。 基於這個想法,如上所述,有必要準備系統來防止人為錯誤並將其引入到專案中,因為如果你沒有系統,則必須記住避免在發行版應用中記錄敏感資訊。

更多日誌方法的資訊,請參考下面的連結:

適用於貢獻者/日誌的程式碼風格指南

http://source.android.com/source/code-style.html#log-sparingly

4.8.3.2 日誌級別和日誌輸出方法的選擇標準

在 Android 中的android.util.Log類中定義了五個日誌級別(ERRORWARNINFODEBUGVERBOSE)。 使用android.util.Log類輸出日誌訊息時,應該選擇最合適的方法,如表 4.8-1 所示,它展示了日誌級別和方法的選擇標準。

表 4.8-1 日誌級別和方法的選擇標準

日誌級別 方法 要輸出的日誌資訊
ERROR Log.e() 應用處於錯誤狀態時,輸出的日誌資訊
WARN Log.w() 應用面臨非預期嚴重情況時,輸出的日誌資訊
INFO Log.i() 與上面不同,用於提示應用狀態中任何值得注意的更改或者結果
DEBUG Log.d() 應用的內部狀態資訊,開發應用時,需要臨時輸出,用於分析特定 bug 的成因
VERBOSE Log.v() 不屬於上面任何一個的日誌資訊。應用開發者以多種目的輸出。例如,輸出伺服器通訊資訊來轉儲。

發行版應用的注意事項:

e/w/i

日誌資訊可能由使用者參考,因此可以在開發版應用和發行版應用中輸出。 因此,敏感資訊不應該在這些級別輸出。

d/v

日誌資訊僅適用於應用開發人員。 因此,這種型別的資訊不應該在發行版的情況下輸出。

更多日誌方法的資訊,請參考下面的連結:

適用於貢獻者/日誌的程式碼風格指南

http://source.android.com/source/code-style.html#log-sparingly

4.8.3.3 DEBUGVERBOSE日誌並不總是自動刪除

以下引用自android.util.Log類 [18] 的開發人員參考。

[18] ttp://developer.android.com/reference/android/util/Log.html

按照囉嗦程度的順序排列,從最少到最多是ERRORWARNINFODEBUGVERBOSE。 除了在開發期間,絕不應該將VERBOSE編譯進應用。 DEBUG日誌被編譯但在執行時剝離。 始終保留ERRORWARNINFO日誌。

在閱讀了上述文章之後,一些開發人員可能會誤解Log類的行為,如下所示。

  • 構建發行版時不編譯Log.v()呼叫,VERBOSE日誌從不輸出。
  • 編譯Log.v()呼叫,但執行時絕不輸出DEBUG日誌。

但是,日誌記錄方法從來不會表現成這樣,並且無論使用除錯模式還是釋出模式編譯,都會輸出所有訊息。 如果仔細閱讀文件,你將能夠認識到,文件的要點與日誌方法的行為無關,而是日誌的基本策略。

在本章中,我們通過使用 ProGuard 引入了示例程式碼以獲得上述的預期結果。

4.8.3.4 從彙編中移除敏感資訊

如果為了刪除Log.d()方法而使用 ProGuard 構建以下程式碼,有必要記住,ProGuard會保留為日誌資訊構造字串的語句(程式碼的第一行),即使它刪除了 Log.d()方法的呼叫(程式碼的第二行)。

String debug_info = String.format("%s:%s", "Sensitive information1", "Sensitive information2");
if (BuildConfig.DEBUG) android.util.Log.d(TAG, debug_info);

以下反彙編顯示了使用 ProGuard 釋出上述程式碼的結果。 實際上,沒有Log.d()呼叫過程,但你可以看到字串一致性定義,例如Sensitive information1,和String#format()方法的呼叫過程,不會被刪除並仍然存在。

const-string v1, "%s:%s"
const/4 v2, 0x2
new-array v2, v2, [Ljava/lang/Object;
const/4 v3, 0x0
const-string v4, "Sensitive information 1"
aput-object v4, v2, v3
const/4 v3, 0x1
const-string v4, "Sensitive information 2"
aput-object v4, v2, v3
invoke-static {v1, v2}, Ljava/lang/String;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang
/String;
move-result-object v0

實際上,找到反彙編 APK 檔案的組裝日誌輸出資訊特定部分並不容易。 但是,在某些處理機密資訊的應用中,這種型別的過程在某些情況下不應保留在 APK 檔案中。 你應該像下面那樣實現你的應用,來避免在位元組碼中保留敏感資訊的後果。 在發行版中,編譯器優化將完全刪除以下程式碼。

if (BuildConfig.DEBUG) {
String debug_info = String.format("%s:%s", " Snsitive information 1", "Sensitive information 2");
if (BuildConfig.DEBUG) android.util.Log.d(TAG, debug_info);
}

此外,ProGuard 無法刪除以下日誌訊息程式碼("result:" + value)

Log.d(TAG, "result:" + value);

在這種情況下,你可以通過以下方式解決問題。

if (BuildConfig.DEBUG) Log.d(TAG, "result:" + value);

4.8.3.5 意圖的內容輸出到了 LogCat

使用活動時,需要注意,因為ActivityManager將意圖的內容輸出到 LogCat。 請參閱“4.1.3.5 使用活動時的日誌輸出”。

4.8.3.6 限制輸出到System.out / err的日誌

System.out / err方法將所有訊息輸出到 LogCat。 即使開發者沒有在他們的程式碼中使用這些方法,Android 也可以向System.out / err傳送一些訊息,例如,在以下情況下,Android 會將堆疊蹤跡傳送到System.err方法。

  • 使用Exception#printStackTrace()
  • 隱式輸出到System.err時(當異常沒有被應用捕獲時,它會由系統提供給Exception#printStackTrace()。)

你應該適當地處理錯誤和異常,因為堆疊蹤跡包含應用的獨特資訊。

我們介紹一種改變System.out / err預設輸出目標的方法。 當你構建發行版應用時,以下程式碼將System.out / err方法的輸出重定向到任何地方。 但是,你應該考慮此重定向是否會導致應用或系統故障,因為程式碼會暫時覆蓋System.out / err方法的預設行為。 此外,這種重定向僅對你的應用有效,對系統程式毫無價值。

OutputRedirectApplication.java

package org.jssec.android.log.outputredirection;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import android.app.Application;

public class OutputRedirectApplication extends Application {

    // PrintStream which is not output anywhere
    private final PrintStream emptyStream = new PrintStream(new OutputStream() {
        public void write(int oneByte) throws IOException {
            // do nothing
        }
    });

    @Override
    public void onCreate() {
        // Redirect System.out/err to PrintStream which doesn`t output anywhere, when release build.
        // Save original stream of System.out/err
        PrintStream savedOut = System.out;
        PrintStream savedErr = System.err;
        // Once, redirect System.out/err to PrintStream which doesn`t output anywhere
        System.setOut(emptyStream);
        System.setErr(emptyStream);
        // Restore the original stream only when debugging. (In release build, the following 1 line is deleted byProGuard.)
        resetStreams(savedOut, savedErr);
    }

    // All of the following methods are deleted byProGuard when release.
    private void resetStreams(PrintStream savedOut, PrintStream savedErr) {
        System.setOut(savedOut);
        System.setErr(savedErr);
    }
}

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.jssec.android.log.outputredirection" >
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:name=".OutputRedirectApplication"
        android:allowBackup="false" >
        <activity
            android:name=".LogActivity"
            android:label="@string/app_name"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

proguard-project.txt

# Prevent from changing class name and method name, etc
-dontobfuscate
# In release build, delete call from Log.d()/v() automatically.
-assumenosideeffects class android.util.Log {
    public static int d(...);
    public static int v(...);
}
# In release build, delete resetStreams() automatically.
-assumenosideeffects class org.jssec.android.log.outputredirection.OutputRedirectApplication {
    private void resetStreams(...);
}

開發版應用(除錯版)和釋出版應用(發行版)之間的 LogCat 輸出差異如下圖 4.8-3 所示。


相關文章