欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

安卓应用安全指南 4.8 输出到 LogCat

程序员文章站 2022-06-28 09:27:46
...

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 文件。

安卓应用安全指南 4.8 输出到 LogCat

开发版应用(调试版本)和发行版应用(发布版本)之间的LogCat 输出差异如下图 4.8-2 所示。

安卓应用安全指南 4.8 输出到 LogCat

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 所示。

安卓应用安全指南 4.8 输出到 LogCat