Fixing a Zip Path Traversal Vulnerability In Android - android

I have uploaded My Application in Google Play Store and Google has given warning that is "Android Security".
In Application, we downloaded the Zip folder and save this Zip folder in internal Storage and than unZip that folder in internal Storage of device.
here is the UnZip Folder Code:
public static void doUnzip(String inputZipFile, String
destinationDirectory, ZipProgressListener zipProgressListener) throws
IOException, RuntimeException {
Log.e(TAG, "doUnzip:inputZipFile: " + inputZipFile);
Log.e(TAG, "doUnzip:destinationDirectory: " + destinationDirectory);
int BUFFER = 6 * 1024;
List zipFiles = new ArrayList();
File sourceZipFile = FileUtils.createValidFile(inputZipFile);
File unzipDestinationDirectory =
FileUtils.createValidFile(destinationDirectory);
unzipDestinationDirectory.mkdir();
String newPath = unzipDestinationDirectory.getAbsolutePath() +
File.separator +
FileUtils.getFileNameWithoutExtension(sourceZipFile.getName());
new File(newPath).mkdir();
ZipFile zipFile;
// Open Zip file for reading
zipFile = new ZipFile(sourceZipFile, ZipFile.OPEN_READ);
int entries = zipFile.size();
int total = 0;
Log.e(TAG, "doUnzip: entries Found !!" + entries);
// Create an enumeration of the entries in the zip file
Enumeration zipFileEntries = zipFile.entries();
if (zipProgressListener != null) {
zipProgressListener.onZipStart();
}
// Process each entry
while (zipFileEntries.hasMoreElements()) {
// grab a zip file entry
ZipEntry entry = (ZipEntry) zipFileEntries.nextElement();
String currentEntry = entry.getName();
Log.i(TAG, "[doUnzip] " + currentEntry);
File file = new File(newPath);
File destFile = new File(newPath, currentEntry);
Log.i(TAG, "doUnzip getCanonicalPath : " +
destFile.getCanonicalPath());
if (Build.VERSION.SDK_INT <= VERSION_CODES.LOLLIPOP) {
Log.i(TAG, "doUnzip: LOLLIPOP");
if
(!destFile.getCanonicalPath().startsWith(destinationDirectory)) {
throw new RuntimeException(destFile.getCanonicalPath() +
" is outside of targetDirectory: " + destinationDirectory);
}
} else {
Log.i(TAG, "doUnzip: Above ");
if(!destFile.getCanonicalPath().contains(file.getName()) &&
!destFile.getCanonicalPath().contains("/")){
throw new RuntimeException(destFile.getCanonicalPath() +
" is outside of targetDirectory: " + destinationDirectory);
}
}
if (currentEntry.endsWith(".zip")) {
zipFiles.add(destFile.getAbsolutePath());
}
// grab file's parent directory structure
File destinationParent = destFile.getParentFile();
// create the parent directory structure if needed
destinationParent.mkdirs();
try {
// extract file if not a directory
if (!entry.isDirectory()) {
BufferedInputStream is = new
BufferedInputStream(zipFile.getInputStream(entry));
int currentByte;
// establish buffer for writing file
byte data[] = new byte[BUFFER];
// write the current file to disk
FileOutputStream fos = new FileOutputStream(destFile);
BufferedOutputStream dest = new BufferedOutputStream(fos,
BUFFER);
// read and write until last byte is encountered
while ((currentByte = is.read(data, 0, BUFFER)) != -1) {
dest.write(data, 0, currentByte);
}
Log.e(TAG, "unzip:outPath: =>" +
destFile.getAbsolutePath() + "\nFile size: " + destFile.length()
/ 1024);
dest.flush();
dest.close();
is.close();
}
int progress = 0;
if (zipProgressListener != null) {
progress = (total++ * 100 / entries);
zipProgressListener.onZipProgressUpdate(progress);
}
Log.e(TAG, "unzip: PROGRESS::" + progress);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
zipFile.close();
for (Object zipFile1 : zipFiles) {
String zipName = (String) zipFile1;
Log.i(TAG, "doUnzip: ");
doUnzip(zipName, destinationDirectory + File.separator +
zipName.substring(0, zipName.lastIndexOf(".zip")),
zipProgressListener);
}
if (zipProgressListener != null) {
Log.i(TAG, "doUnzip: " + sourceZipFile.getName());
zipProgressListener.onZipCompleted(destinationDirectory +
File.separatorChar + sourceZipFile.getName().substring(0,
sourceZipFile.getName().lastIndexOf(".zip")));
}
}
Here is Google warning :
This information is intended for developers with the app(s) that contain unsafe unzipping patterns, which may potentially lead to a Zip Path Traversal attack. Locations of vulnerable app classes containing unsafe unzipping patterns can be found in the Play Console notification for your app.
Additional details
Zip files can contain an entry (file or directory) having path traversal characters (“../”) in its name. If developers unzip such zip file entries without validating their name, it can potentially cause a path traversal attack, leading to writes in arbitrary directories or even overwriting the files in the app's private folders.
We recommend fixing this issue in your app by checking if canonical paths to unzipped files are underneath an expected directory. Specifically, before using a File object created using the return value of ZipEntry's getName() method, always check if the return value of File.GetCanonicalPath() belongs to the intended directory path. For example:
InputStream is = new InputStream(untrustedFileName);
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is));
while((ZipEntry ze = zis.getNextEntry()) != null) {
File f = new File(DIR, ze.getName());
String canonicalPath = f.getCanonicalPath();
if (!canonicalPath.startsWith(DIR)) {
// SecurityException
}
// Finish unzipping…
}
How can I solve this warning in Above Android OS-6?

Check vulnerability like this
InputStream is = new InputStream(untrustedFileName);
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is));
while((ZipEntry ze = zis.getNextEntry()) != null) {
File outputFile = new File(outputDir, ze.getName());
try {
ensureZipPathSafety(outputFile, outputDir);
} catch (Exception e) {
e.printStackTrace();
return;
}
// Finish unzipping…
}
private void ensureZipPathSafety(final File outputFile, final String destDirectory) throws Exception {
String destDirCanonicalPath = (new File(destDirectory)).getCanonicalPath();
String outputFilecanonicalPath = outputFile.getCanonicalPath();
if (!outputFileCanonicalPath.startsWith(destDirCanonicalPath)) {
throw new Exception(String.format("Found Zip Path Traversal Vulnerability with %s", canonicalPath));
}
}

Related

How to create a shared folder usable by my android app?

I've got a question that probably borders on opinion, but I've not any related questions or documentation that answers, so I feel like it's a fair one to ask.
I'm trying to build an android app which modifies music files, and what I'd like to do is have a shared folder so that the files and the results can be accessible and shared. I'd like it if it was among the other folders like Music, Downloads, Movies, etc, or even under Music since it's music related. However this seems like it's a security no no in Android, as after I've made something and put it in there I have to use an intent to access it again, where as I'd rather just be able to open the files and not have a permissions based fiasco. Maybe some type of symbolic link like in Linux that pointed to my apps internal folder could be used, but of this I'm still uncertain.
In any case, is there a way I should go about this? If so, are there some resources I could be pointed to?
Thank you in advance to anyone who takes this up!
Edit for CommonsWare:
I used the following to create the folder:
File mediaStorageDir = new File(Environment.getExternalStorageDirectory(), APP_NAME);
And this to copy files from elsewhere to there:
public void copyFileToHomeDirectory(Uri uri)
{
try
{
ContentResolver contentResolver = getApplicationContext().getContentResolver();
String fileName = queryName(contentResolver, uri);
//Get file extension
String fileType = fileName.substring(fileName.length() - 4, fileName.length());
if(fileType.equalsIgnoreCase(MP3_EXTENSION))
{
String path = Environment.getExternalStorageDirectory() + APP_FOLDER;
InputStream in = contentResolver.openInputStream(uri);
File outputFile = new File(path + File.separator + fileName);
outputFile.createNewFile();
OutputStream out = new FileOutputStream(outputFile);
//First we crack open the file to copy it's contents:
byte[] buffer = new byte[KB_SIZE];
int read;
while ((read = in.read(buffer)) != -1)
{
out.write(buffer, 0, read);
}
in.close();
in = null;
// write the output file (You have now copied the file)
out.flush();
out.close();
out = null;
}
}
catch(FileNotFoundException fnfe)
{
Log.e(TAG, "FileNotFoundException");
Log.e(TAG, Log.getStackTraceString(fnfe));
}
catch(IOException ioe)
{
Log.e(TAG, "IOException");
Log.e(TAG, Log.getStackTraceString(ioe));
}
catch(Exception e)
{
Log.e(TAG, "General Exception");
Log.e(TAG, Log.getStackTraceString(e));
}
}
I've tried other methods that I've overwritten in the process, but accessing the files to be used again I need something like this:
public void openDirectory(View view)
{
// Choose a directory using the system's file picker.
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
// Provide read access to files and sub-directories in the user-selected
// directory.
//intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
//intent.addCategory(Intent.CATEGORY_OPENABLE);
// Optionally, specify a URI for the directory that should be opened in
// the system file picker when it loads.
//intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uriToLoad);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*"); //use image/* for photos, etc.
//The result of this code will be calling the onActivityResult function below
startActivityForResult(intent, REQUEST_MUSIC_DIR);
}
Edit2:
I've reorganized the folders to what I think I should be doing so that I can work with the files freely, however, even in my internal cache storage (getCacheDir() + folder_name) either isn't letting me create the files (outputFile.createNewFile doesn't throw an error) or it isn't letting me open them when I go to get a directory listing.
Here's my code for creating the file:
String path = getCacheDir() + MY_SUB_FOLDER;
//uri is obtained through ACTION_OPEN_DOCUMENT intent
InputStream in = contentResolver.openInputStream(uri);
File outputFile = new File(path + "/" + fileName);
outputFile.createNewFile();
Log.i(TAG, "The new file's directory/path is: " + outputFile.getAbsolutePath());
//NOTE: This is returning /data/user/0/com.example.myapplication/cache/MY_SUB_FOLDER/file_name.mp3
OutputStream out = new FileOutputStream(outputFile);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1)
{
out.write(buffer, 0, read);
}
in.close();
in = null;
out.flush();
out.close();
out = null;
This is my code for attempting to open and read these newly created files
File directory = new File(getCacheDir(), MY_SUB_FOLDER);
Log.i(TAG, "This is the directory we're trying to get the files from: " + directory.getAbsolutePath());
//NOTE: This returns /data/user/0/com.example.myapplication/cache/MY_SUB_FOLDER
File[] files = directory.listFiles();
if(files != null)
{
for(int i = 0; i < files.length; i++)
{
Log.d(TAG, "Files found: " + files[i].getAbsolutePath());
}
}
The files variable isn't null but it's length is 0 and no files are found.
Edit3:
I am catching the exceptions and logging any stack traces, which currently returns nothing.
catch(FileNotFoundException fnfe)
{
Log.i(TAG, "FileNotFoundException");
Log.i(TAG, Log.getStackTraceString(fnfe));
}
catch(IOException ioe)
{
Log.i(TAG, "IOException");
Log.i(TAG, Log.getStackTraceString(ioe));
}
catch(Exception e)
{
Log.i(TAG, "General Exception");
Log.i(TAG, Log.getStackTraceString(e));
}

Cannot get the real file path - Android Oreo 8+

I'm downloading a file using Download Manager and saving into Download Folder.
After download finish i'm picking up the folder path like this way:
int uriIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
String downloadedPackageUriString = cursor.getString(uriIndex);
Then i need to use this path to unzip the file downloaded. The code to unzip it is below:
SouceFile is the path from downloadmanager.
unzip(String sourceFile, String destinationFolder)
try {
zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(sourceFile)));
ZipEntry ze;
int count;
byte[] buffer = new byte[BUFFER_SIZE];
while ((ze = zis.getNextEntry()) != null) {
String fileName = ze.getName();
fileName = fileName.substring(fileName.indexOf("/") + 1);
File file = new File(destinationFolder, fileName);
File dir = ze.isDirectory() ? file : file.getParentFile();
Log.i("MainService", "Unzipping fileName: " + fileName);
file_path = destinationFolder + "/" + fileName;
if (!dir.isDirectory() && !dir.mkdirs())
throw new FileNotFoundException("Invalid path: " + dir.getAbsolutePath());
if (ze.isDirectory()) continue;
FileOutputStream fout = new FileOutputStream(file);
try {
while ((count = zis.read(buffer)) != -1) {
fout.write(buffer, 0, count);
}
} finally {
fout.close();
}
list_filenames.add(file_downloaded);
}
Log.d("MainService", "TAM:" + tam);
} catch (IOException ioe) {
Log.d("MainService", "Oiiiiiiiiii " + ioe);
return list_filenames;
} finally {
if (zis != null)
try {
zis.close();
} catch (IOException e) {
}
}
The code to unzip works on Android 6 (My phone), but on Android Oreo + it doesnt.
I'm getting (No such file or directory) from zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(sourceFile)));
Reinforcing: I'm not getting this error on Android 6. Just on 8+
Thanks for any help.
I tried some suggestions from other guys here with similar problem, but doesnt works for me.
I'm not getting this error on Android 6
At most, you are not getting this error on the one device that you tested on Android 6.0. There are many device models running Android 6.0, not just one.
COLUMN_LOCAL_URI is supposed to give you a string representation of a Uri. That will not work with FileInputStream, because a Uri is not a file.
Replace:
zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(sourceFile)));
with:
zis = new ZipInputStream(new BufferedInputStream(cr.openInputStream(Uri.parse(sourceFile))));
...where cr is a ContentResolver that you get by calling getContentResolver() on some Context.

Files.list returns null for android data/data device is rooted

I'm trying to generate a list of all the files in a particular directory in /data/data/ e.g. /data/data/com.package.ect so that I can zip them. My device is rooted and I've granted Super User to my app. When pass the path to the directory it gets past the if statement, so it recognises that what I'm trying to access is a directory, yet File.list() returns null. I'm assuming I'm still lacking some sort of permission.
/**
* Traverse a directory and get all files,
* and add the file into fileList
* #param node file or directory
*/
public void generateFileList(File node) {
//add file only
if (node.isFile()) {
fileList.add(generateZipEntry(node.getAbsoluteFile().toString()));
}
if (node.isDirectory()) {
String[] subNote = node.list();
for (String filename : subNote) {
generateFileList(new File(node, filename));
}
}
}
*EDIT: As requested where generateFileList is called
public void zipDirectory(String outputPath){
byte[] buffer = new byte[1024];
try{
checkStorageDirExists(SDCARD, STORAGE_LOCATION);
generateFileList(new File(path));
FileOutputStream fos = new FileOutputStream(SDCARD + STORAGE_LOCATION + outputPath);
ZipOutputStream zos = new ZipOutputStream(fos);
setZipFileName(path);
Log.i(TAG, "Output to Zip : " + outputPath);
for(String file : this.fileList){
System.out.println("File Added : " + file);
ZipEntry ze= new ZipEntry(file);
zos.putNextEntry(ze);
FileInputStream in =
new FileInputStream(path + "/" + file);
int len;
while ((len = in.read(buffer)) > 0) {
zos.write(buffer, 0, len);
}
in.close();
}
zos.closeEntry();
zos.close();
Log.i(TAG, "Zip of " + getZipFileName() +" Completed");
}catch(IOException ex){
ex.printStackTrace();
}

Android unzip open failed: ENOENT (No such file or directory)

I am using the following code to unzip a set of files (containing folders as well):
private boolean unpackZip(String path, String zipname)
{
InputStream is;
ZipInputStream zis;
try
{
String filename;
is = new FileInputStream(path + zipname);
zis = new ZipInputStream(new BufferedInputStream(is));
ZipEntry ze;
byte[] buffer = new byte[1024];
int count;
while ((ze = zis.getNextEntry()) != null)
{
// zapis do souboru
filename = ze.getName();
// Need to create directories if not exists, or
// it will generate an Exception...
if (ze.isDirectory()) {
File fmd = new File(path + filename);
fmd.mkdirs();
continue;
}
FileOutputStream fout = new FileOutputStream(path + filename);
// cteni zipu a zapis
while ((count = zis.read(buffer)) != -1)
{
fout.write(buffer, 0, count);
}
fout.close();
zis.closeEntry();
}
zis.close();
}
catch(IOException e)
{
e.printStackTrace();
return false;
}
return true;
}
The code fails on FileOutputStream fout = new FileOutputStream(path + filename) with the error:
java.io.FileNotFoundException: /storage/emulated/0/BASEFOLDER/FOLDER1/FILE.png
BASEFOLDER already exists, that is where I am trying to unzip the folder to. If I manually (or programmatically) create FOLDER1, the code runs fine and successfully unzips. I believe it is crashing because the very first file (ze) is named FOLDER1/FILE.png and FOLDER1 hasn't been created yet. How do I get around this? I know other people have used this code, I find it unlikely that it randomly doesn't work for me...
Have you got this in your AndroidManifest.xml file?
Add Write to external Storage permission
uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
I had the same problem. After several investigation I found that. put following single line in your code:
if (ze.isDirectory()) {
File fmd = new File(path + filename);
fmd.mkdirs();
zis.closeEntry(); // <<<<<< ADD THIS LINE
continue;
}
Sometime the extract files has been extracted before its parent directory is created, for example:
File A inside directory B. But B directory is not created, index of files listing below cause the issue:
dir_b/file_a.txt
dir_b/
dir_b/file_c.txt
So, to sure directory created before file extracting, you need to create parent directories first, for example:
val targetFile = File(tempOutputDir, zipEntry.name)
if (zipEntry.isDirectory) {
targetFile.mkdirs()
} else {
try {
try {
targetFile.parentFile?.mkdirs() // <-- ADD THIS LINE
} catch (exception: Exception) {
Log.e("ExampleApp", exception.localizedMessage, exception)
}
val bufferOutputStream = BufferedOutputStream(
FileOutputStream(targetFile)
)
val buffer = ByteArray(1024)
var read = zipInputStream.read(buffer)
while (read != -1) {
bufferOutputStream.write(buffer, 0, read)
read = zipInputStream.read(buffer)
}
bufferOutputStream.close()
} catch (exception: Exception) {
Log.e("ExampleApp", exception.localizedMessage, exception)
}
}

How to compress a folder to make docx file in android?

I'm trying to make an Android application that can open a docx file to read, edit and save it.
My idea is to extract all the xml file within the archive to a temp folder. In this folder we can edit the content of the docx in /word/document.xml. The problem is when I compress this temp folder to make a new docx file and replace the old file, inside the new docx archive the path is like /mnt/sdcard/temp/"all files xml go here" while the xml files should be in the first level.
Can anybody help me to go through this? here is the method to compress the temp directory
Note: dir2zip argument's value I use is /mnt/sdcard/temp/***.docx
public void zipDir(String dir2zip, ZipOutputStream zos)
{
try
{
//create a new File object based on the directory we
//have to zip File
File zipDir = new File(dir2zip);
//get a listing of the directory content
String[] dirList = zipDir.list();
byte[] readBuffer = new byte[2156];
int bytesIn = 0;
//loop through dirList, and zip the files
for(int i=0; i<dirList.length; i++)
{
File f = new File(zipDir, dirList[i]);
if(f.isDirectory())
{
//if the File object is a directory, call this
//function again to add its content recursively
String filePath = f.getPath();
zipDir(filePath, zos);
//loop again
continue;
}
//if we reached here, the File object f was not a directory
//create a FileInputStream on top of f
FileInputStream fis = new FileInputStream(f);
//create a new zip entry
ZipEntry anEntry = new ZipEntry(f.getPath());
//place the zip entry in the ZipOutputStream object
zos.putNextEntry(anEntry);
//now write the content of the file to the ZipOutputStream
while((bytesIn = fis.read(readBuffer)) != -1)
{
zos.write(readBuffer, 0, bytesIn);
}
//close the Stream
fis.close();
}
}
catch(Exception e)
{
//handle exception
}
}
I have managed to fix it by myself. The problem is in this line:
File f = new File(zipDir, dirList[i]);
It should be
File f = new File(dirList[i]);
If the argument zipDir is included, the absolute path to the directory will be used in the archive!
I have now managed to get the original poster's code working on Mac and Windows by making the following two modifications:
1: add a ZipEntry for each directory: do not simply ignore it
2: remove the directory name from the ZipEntry name
Note: zipinfo is useful
This is a program that works for me:
import java.io.*;
import java.util.zip.*;
public class zipdoc
{
String savedDir = null;
public void zipDir(String dir2zip, ZipOutputStream zos)
{
try
{
if (savedDir == null)
savedDir = dir2zip;
// create a new File object based on the directory we
// have to zip File
File zipDir = new File(dir2zip);
//get a listing of the directory content
String[] dirList = zipDir.list();
byte[] readBuffer = new byte[2156];
int bytesIn = 0;
// loop through dirList, and zip the files
for (int i=0; i<dirList.length; i++)
{
File f = new File(zipDir, dirList[i]);
if (f.isDirectory())
{
// if the File object is a directory, call this
// function again to add its content recursively
System.out.println("Adding dir: " + f);
// create a new zip entry
ZipEntry anEntry = new ZipEntry(f.getPath().substring(savedDir.length()+1) + "/");
// place the zip entry in the ZipOutputStream object
zos.putNextEntry(anEntry);
String filePath = f.getPath();
zipDir(filePath, zos);
// loop again
continue;
}
else if (!f.getName().equals(".DS_Store"))
{
// if we reached here, the File object f was not a directory
// and it's not the MacOSX special .DS_Store
// create a FileInputStream on top of f
System.out.println("Adding file: " + f);
FileInputStream fis = new FileInputStream(f);
// create a new zip entry
ZipEntry anEntry = new ZipEntry(f.getPath().substring(savedDir.length()+1));
// place the zip entry in the ZipOutputStream object
zos.putNextEntry(anEntry);
// now write the content of the file to the ZipOutputStream
while((bytesIn = fis.read(readBuffer)) != -1)
{
zos.write(readBuffer, 0, bytesIn);
}
// close the Stream
fis.close();
}
}
}
catch(Exception e)
{
// handle exception
System.out.println(e);
}
}
public void zipit(String inDir, String outFile)
{
try {
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(outFile)));
zos.setMethod(0);
zos.setMethod(ZipOutputStream.DEFLATED);
zos.setLevel(0);
zipDir(inDir, zos);
zos.finish();
zos.close();
}
catch (Exception e)
{
System.out.println(e);
}
}
public static void main (String args[]) {
zipdoc z1 = new zipdoc();
// Check there are sufficient params if desired
// first param is directory to be 'zipped', second is resulting
// filename (??.docx)
// eg java zipdoc dir1 newDoc.docx
z1.zipit(args[0], args[1]);
System.out.println("Finished creating " + args[1]);
}
}

Categories

Resources