Androidacy WebView hardening, and some bug fixes.

pull/85/head
Fox2Code 2 years ago
parent 355099ff9b
commit 7a6aa28277

@ -9,6 +9,7 @@ import android.widget.Toast;
import androidx.annotation.DrawableRes;
import androidx.appcompat.app.AlertDialog;
import com.fox2code.mmm.androidacy.AndroidacyUtil;
import com.fox2code.mmm.compat.CompatActivity;
import com.fox2code.mmm.compat.CompatDisplay;
import com.fox2code.mmm.installer.InstallerInitializer;
@ -160,7 +161,7 @@ public enum ActionButtonType {
public void doAction(ImageButton button, ModuleHolder moduleHolder) {
String config = moduleHolder.getMainModuleConfig();
if (config == null) return;
if (config.startsWith("https://www.androidacy.com/")) {
if (AndroidacyUtil.isAndroidacyLink(config)) {
IntentHelper.openUrlAndroidacy(button.getContext(), config, true);
} else {
IntentHelper.openConfig(button.getContext(), config);

@ -7,6 +7,7 @@ import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
@ -33,6 +34,7 @@ import com.fox2code.mmm.utils.IntentHelper;
* Per Androidacy repo implementation agreement, no request of this WebView shall be modified.
*/
public class AndroidacyActivity extends CompatActivity {
private static final String TAG = "AndroidacyActivity";
private static final String REFERRER = "utm_source=FoxMMM&utm_medium=app";
static {
@ -42,6 +44,7 @@ public class AndroidacyActivity extends CompatActivity {
}
WebView webView;
AndroidacyWebAPI androidacyWebAPI;
boolean backOnResume;
@Override
@ -51,16 +54,14 @@ public class AndroidacyActivity extends CompatActivity {
Intent intent = this.getIntent();
Uri uri;
if (!MainApplication.checkSecret(intent) ||
(uri = intent.getData()) == null ||
!uri.getHost().endsWith(".androidacy.com")) {
(uri = intent.getData()) == null) {
Log.w(TAG, "Impersonation detected");
this.forceBackPressed();
return;
}
String url = uri.toString();
int i;
if (!url.startsWith("https://") || // Checking twice
(i = url.indexOf("/", 8)) == -1 ||
!url.substring(8, i).endsWith(".androidacy.com")) {
if (!AndroidacyUtil.isAndroidacyLink(url, uri)) {
Log.w(TAG, "Calling non androidacy link in secure WebView: " + url);
this.forceBackPressed();
return;
}
@ -112,9 +113,9 @@ public class AndroidacyActivity extends CompatActivity {
@Override
public boolean shouldOverrideUrlLoading(
@NonNull WebView view, @NonNull WebResourceRequest request) {
// Don't open non andoridacy urls inside WebView
// Don't open non Androidacy urls inside WebView
if (request.isForMainFrame() && !(request.getUrl().getScheme().equals("intent") ||
request.getUrl().getHost().endsWith(".androidacy.com"))) {
AndroidacyUtil.isAndroidacyLink(request.getUrl()))) {
IntentHelper.openUrl(view.getContext(), request.getUrl().toString());
return true;
}
@ -159,7 +160,7 @@ public class AndroidacyActivity extends CompatActivity {
return true;
}
});
this.webView.addJavascriptInterface(
this.webView.addJavascriptInterface(androidacyWebAPI =
new AndroidacyWebAPI(this, allowInstall), "mmm");
this.webView.loadUrl(url);
}
@ -180,6 +181,8 @@ public class AndroidacyActivity extends CompatActivity {
if (this.backOnResume) {
this.backOnResume = false;
this.forceBackPressed();
} else if (this.androidacyWebAPI != null) {
this.androidacyWebAPI.consumedAction = false;
}
}
}

@ -0,0 +1,24 @@
package com.fox2code.mmm.androidacy;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class AndroidacyUtil {
public static boolean isAndroidacyLink(@Nullable Uri uri) {
return uri != null && isAndroidacyLink(uri.toString(), uri);
}
public static boolean isAndroidacyLink(@Nullable String url) {
return url != null && isAndroidacyLink(url, Uri.parse(url));
}
static boolean isAndroidacyLink(@NonNull String url,@NonNull Uri uri) {
int i; // Check both string and Uri to mitigate parse exploit
return url.startsWith("https://") &&
(i = url.indexOf("/", 8)) != -1 &&
url.substring(8, i).endsWith(".androidacy.com") &&
uri.getHost().endsWith(".androidacy.com");
}
}

@ -13,6 +13,7 @@ import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleInfo;
import com.fox2code.mmm.manager.ModuleManager;
import com.fox2code.mmm.utils.Files;
import com.fox2code.mmm.utils.Hashes;
import com.fox2code.mmm.utils.IntentHelper;
import java.io.File;
@ -23,21 +24,30 @@ public class AndroidacyWebAPI {
private static final String TAG = "AndroidacyWebAPI";
private final AndroidacyActivity activity;
private final boolean allowInstall;
boolean consumedAction;
public AndroidacyWebAPI(AndroidacyActivity activity, boolean allowInstall) {
this.activity = activity;
this.allowInstall = allowInstall;
}
public void forceQuitRaw(String error) {
Toast.makeText(this.activity, error, Toast.LENGTH_LONG).show();
this.activity.runOnUiThread(this.activity::forceBackPressed);
this.activity.backOnResume = true; // Set backOnResume just in case
}
@JavascriptInterface
public void forceQuit(String error) {
Toast.makeText(this.activity, error, Toast.LENGTH_LONG).show();
this.activity.runOnUiThread(
this.activity::forceBackPressed);
if (this.consumedAction) return;
this.consumedAction = true;
this.forceQuitRaw(error);
}
@JavascriptInterface
public void cancel() {
if (this.consumedAction) return;
this.consumedAction = true;
this.activity.runOnUiThread(
this.activity::forceBackPressed);
}
@ -47,6 +57,8 @@ public class AndroidacyWebAPI {
*/
@JavascriptInterface
public void openUrl(String url) {
if (this.consumedAction) return;
this.consumedAction = true;
Log.d(TAG, "Received openUrl request: " + url);
if (Uri.parse(url).getScheme().equals("https")) {
IntentHelper.openUrl(this.activity, url);
@ -82,19 +94,25 @@ public class AndroidacyWebAPI {
*/
@JavascriptInterface
public void install(String moduleUrl, String installTitle,String checksum) {
if (!this.canInstall()) {
if (this.consumedAction || !this.canInstall()) {
return;
}
this.consumedAction = true;
Log.d(TAG, "Received install request: " +
moduleUrl + " " + installTitle + " " + checksum);
Uri uri = Uri.parse(moduleUrl);
if (uri.getScheme().equals("https") && uri.getHost().endsWith(".androidacy.com")) {
this.activity.backOnResume = true;
IntentHelper.openInstaller(this.activity,
moduleUrl, installTitle, null, checksum);
} else {
this.activity.forceBackPressed();
if (!AndroidacyUtil.isAndroidacyLink(moduleUrl, uri)) {
this.forceQuitRaw("Non Androidacy module link used on Androidacy");
return;
}
if (checksum != null) checksum = checksum.trim();
if (!Hashes.checkSumValid(checksum)) {
this.forceQuitRaw("Androidacy didn't provided a valid checksum");
return;
}
this.activity.backOnResume = true;
IntentHelper.openInstaller(this.activity,
moduleUrl, installTitle, null, checksum);
}
/**
@ -137,7 +155,25 @@ public class AndroidacyWebAPI {
*/
@JavascriptInterface
public void hideActionBar() {
this.activity.hideActionBar();
if (this.consumedAction) return;
this.consumedAction = true;
this.activity.runOnUiThread(() -> {
this.activity.hideActionBar();
this.consumedAction = false;
});
}
/**
* Show action bar if not visible, the action bar is only visible by default on notes.
*/
@JavascriptInterface
public void showActionBar() {
if (this.consumedAction) return;
this.consumedAction = true;
this.activity.runOnUiThread(() -> {
this.activity.showActionBar();
this.consumedAction = false;
});
}
/**
@ -147,8 +183,7 @@ public class AndroidacyWebAPI {
public boolean isAndroidacyModule(String moduleId) {
LocalModuleInfo localModuleInfo = ModuleManager.getINSTANCE().getModules().get(moduleId);
return localModuleInfo != null && ("Androidacy".equals(localModuleInfo.author) ||
(localModuleInfo.config != null &&
localModuleInfo.config.startsWith("https://www.androidacy.com/")));
AndroidacyUtil.isAndroidacyLink(localModuleInfo.config));
}
/**
@ -157,7 +192,8 @@ public class AndroidacyWebAPI {
*/
@JavascriptInterface
public String getAndroidacyModuleFile(String moduleId, String moduleFile) {
if (moduleFile == null || !this.isAndroidacyModule(moduleId)) return "";
if (moduleFile == null || this.consumedAction ||
!this.isAndroidacyModule(moduleId)) return "";
File moduleFolder = new File("/data/adb/modules/" + moduleId);
File absModuleFile = new File(moduleFolder, moduleFile).getAbsoluteFile();
if (!absModuleFile.getPath().startsWith(moduleFolder.getPath())) return "";
@ -175,7 +211,8 @@ public class AndroidacyWebAPI {
*/
@JavascriptInterface
public boolean setAndroidacyModuleMeta(String moduleId, String content) {
if (content == null || !this.isAndroidacyModule(moduleId)) return false;
if (content == null || this.consumedAction ||
!this.isAndroidacyModule(moduleId)) return false;
File androidacyMetaFile = new File(
"/data/adb/modules/" + moduleId + "/.androidacy");
try {

@ -164,6 +164,23 @@ public class CompatActivity extends AppCompatActivity {
}
}
public void showActionBar() {
androidx.appcompat.app.ActionBar compatActionBar;
try {
compatActionBar = this.getSupportActionBar();
} catch (Exception e) {
Log.e(TAG, "Failed to call getSupportActionBar", e);
compatActionBar = null; // Allow fallback to builtin actionBar.
}
if (compatActionBar != null) {
compatActionBar.show();
} else {
android.app.ActionBar actionBar = this.getActionBar();
if (actionBar != null)
actionBar.show();
}
}
@Dimension @Px
public int getActionBarHeight() {
androidx.appcompat.app.ActionBar compatActionBar;

@ -132,6 +132,10 @@ public class InstallerActivity extends CompatActivity {
this.progressIndicator.setProgressCompat(progress, true);
});
});
this.runOnUiThread(() -> {
this.progressIndicator.setVisibility(View.GONE);
this.progressIndicator.setIndeterminate(true);
});
if (this.canceled) return;
if (checksum != null && !checksum.isEmpty()) {
Log.d(TAG, "Checking for checksum: " + checksum);
@ -178,8 +182,6 @@ public class InstallerActivity extends CompatActivity {
errMessage = "Failed to patch module zip";
this.runOnUiThread(() -> {
this.installerTerminal.addLine("- Patching " + name);
this.progressIndicator.setVisibility(View.GONE);
this.progressIndicator.setIndeterminate(true);
});
Log.i(TAG, "Patching: " + moduleCache.getName());
try (OutputStream outputStream = new FileOutputStream(moduleCache)) {

@ -42,14 +42,14 @@ public class InstallerInitializer extends Shell.Initializer {
}
public static void tryGetMagiskPathAsync(Callback callback,boolean forceCheck) {
String MAGISK_PATH = InstallerInitializer.MAGISK_PATH;
if (MAGISK_PATH != null && !forceCheck) {
callback.onPathReceived(MAGISK_PATH);
return;
}
final String MAGISK_PATH = InstallerInitializer.MAGISK_PATH;
Thread thread = new Thread("Magisk GetPath Thread") {
@Override
public void run() {
if (MAGISK_PATH != null && !forceCheck) {
callback.onPathReceived(MAGISK_PATH);
return;
}
int error;
String MAGISK_PATH = null;
try {

@ -84,4 +84,25 @@ public class Hashes {
Log.d(TAG, "Checksum result (data: " + hash+ ",expected: " + checksum + ")");
return hash.equals(checksum.toLowerCase(Locale.ROOT));
}
public static boolean checkSumValid(String checksum) {
if (checksum == null) return false;
switch (checksum.length()) {
case 0:
default:
return false;
case 32:
case 40:
case 64:
case 128:
final int len = checksum.length();
for (int i = 0; i < len; i++) {
char c = checksum.charAt(i);
if (c < '0' || c > 'f') return false;
if (c > '9' && // Easier working with bits
(c & 0b01011111) < 'A') return false;
}
return true;
}
}
}

Loading…
Cancel
Save