用户
搜索
  • TA的每日心情

    2017-12-28 15:34
  • 签到天数: 5 天

    连续签到: 1 天

    [LV.2]偶尔看看

    版主

    Rank: 7Rank: 7Rank: 7

    78

    主题

    269

    帖子

    1861

    魔法币
    收听
    0
    粉丝
    70
    注册时间
    2016-6-21

    i春秋认证春秋巡逻i春秋签约作者春秋游侠春秋文阁

    发表于 2019-1-20 22:04:40 117996
    本帖最后由 Sp4ce 于 2019-1-20 14:08 编辑

    0x01 漏洞描述

    ES File Explorer File Manager application for Android(ES文件浏览器或文件管理器)是一款基于Android系统的多功能手机文件、程序和进程管理器,它支持在手机、电脑、远程和蓝牙间浏览管理文件。 基于Android平台的ES File Explorer File Manager application 4.1.9.7.4及之前版本中存在安全漏洞,该漏洞源于ES应用程序在运行一次之后(CVE-2019-6447),该端口并未关闭依旧可以通过HTTP协议接收JSON数据。攻击者可通过向TCP 59777端口发送请求利用该漏洞读取任意文件或执行应用程序。

    0x02 复现

    POC

    Github地址:https://github.com/fs0c131y/ESFileExplorerOpenPortVuln

    步骤

    1. 安装ES文件管理器,版本低于4.1.9.7.4并启动,不执行任何操作
    2. 接入与电脑同一局域网
    3. 对指定设备进行扫描
      nmap -sS -p 1-65535 -v 192.168.2.63
    4. 验证POC
      执行命令python3 poc.py --cmd getDeviceInfo --network 192.168.2.

      回显设备信息和FTP端口号
      复现成功

      漏洞分析

      从一些渠道下载到了低于官网的软件安装包,然后解包后将3个classes.dex转为jar
      最终目录图如下

      用JD-GUI打开JAR包

      对POC中的关键字如59777command等进行全局搜索,最终取得执行漏洞的关键代码和,具体如下
      漏洞的包路径4.1.84class2.-dex2jar.jar\com\estrongs\android\f\a.class

      public c.b a(String paramString1, String paramString2, Properties paramProperties1, Properties paramProperties2, Properties paramProperties3)
      {
        if (paramString1.startsWith("/estrongs_filemgr_oauth_result"))
        {
          paramString1 = CreateOAuthNetDisk.b();
          if (paramString1 != null) {
            paramString1.a(paramProperties2);
          }
          return null;
        }
        if (paramString2.equals("POST"))//判断发送方法是否为POST
        {
          localObject = new String(g());
          try
          {
            localObject = new JSONObject((String)localObject);//JSONG解码
            String str = ((JSONObject)localObject).getString("command");//获取JSON中command字段值
            if (str.equals("listFiles")) {//获取文件列表
              return b(paramString1);
            }
            if (str.equals("listPics")) {//获取图片列表
              return d();
            }
            if (str.equals("listVideos")) {//获取视频列表
              return e();
            }
            if (str.equals("listAudios")) {//获取音频列表
              return f();
            }
            if (str.equals("listApps")) {//获取软件列表
              return a(0);
            }
            if (str.equals("listAppsSystem")) {//获取系统软件列表
              return a(1);
            }
            if (str.equals("listAppsPhone")) {//获取电话软件列表
              return a(2);
            }
            if (str.equals("listAppsSdcard")) {//获取安装在SD卡中的软件列表
              return a(3);
            }
            if (str.equals("listAppsAll")) {//获取全部APP
              return a(4);
            }
            if (str.equals("getAppThumbnail")) {//获取APP缩略图
              return d((JSONObject)localObject);
            }
            if (str.equals("appLaunch")) {//启动APP
              return a((JSONObject)localObject);
            }
            if (str.equals("appPull")) {//下载APP
              return c((JSONObject)localObject);
            }
            if (str.equals("getDeviceInfo"))//获取设备信息
            {
              paramString1 = b((JSONObject)localObject);
              return paramString1;
            }
          }
          catch (JSONException paramString1)
          {
            paramString1.printStackTrace();
            return new c.b(this, "500 Internal Server Error", "text/plain", paramString1.toString());
          }
        }
        Object localObject = ah.bL(paramString1);
        if ((localObject == null) || (ah.I((String)localObject) == 0))
        {
          if (localObject == null) {
            return super.a(paramString1, paramString2, paramProperties1, paramProperties2, paramProperties3);
          }
          return super.a((String)localObject, paramString2, paramProperties1, paramProperties2, paramProperties3);
        }
        paramString1 = paramProperties1.getProperty("range");
        if ((paramString1 != null) && (paramString1.startsWith("bytes=")))
        {
          paramString2 = paramString1.substring("bytes=".length());
          int i = paramString2.indexOf('-');
          paramString1 = paramString2;
          if (i > 0) {
            paramString1 = paramString2.substring(0, i);
          }
        }
        for (;;)
        {
          try
          {
            l = Long.parseLong(paramString1);
            return a((String)localObject, l, 0L);
          }
          catch (NumberFormatException paramString1)
          {
            l = 0L;
            continue;
          }
          long l = 0L;
        }
      }

    由于漏洞应用条件是在同一个局域网下,所以应用在获取完传入的方法【指令】后调用class-dex2jar.jar\com\estrongs\android\util\af,class包中的bM方法验证URI地址

    public static String bM(String paramString)
      {
        int i3 = 1;
        if (paramString == null) {
          return null;
        }
        String str1 = ad.a();
        int i2;
        if ((paramString.startsWith("http://127.0.0.1")) || (paramString.startsWith("http://" + str1)))
        {
          i1 = paramString.indexOf("/", 7);
          str1 = paramString;
          if (i1 >= 0) {
            str1 = paramString.substring(i1);
          }
          if (!str1.startsWith("/")) {
            break label116;
          }
          i2 = str1.indexOf('/', 1);
        }

    并且同时调用class3-dex2jar\com\estrongs\android\util\ad.class中的a方法检测当前环境是否处于WIFI环境下

    public class ad
    {
      public static String a()
      {
        Object localObject = FexApplication.a();
        if (b())
        {
          localObject = ((WifiManager)((Context)localObject).getSystemService("wifi")).getConnectionInfo();
          if ((localObject == null) || (((WifiInfo)localObject).getIpAddress() == 0)) {
            return o.a();
          }
          int i = ((WifiInfo)localObject).getIpAddress();
          return (i & 0xFF) + "." + (i >> 8 & 0xFF) + "." + (i >> 16 & 0xFF) + "." + (i >> 24 & 0xFF);
        }
        return o.a();
      }

    当传入的commandlistFiles时,会调用b()函数获取文件信息并以Json格式返回,相关代码如下

      public c.b b(String paramString)
      {
        if (ah.I(paramString) == 0) {}
        for (;;)
        {
          int i;
          try
          {
            Object localObject = new File(paramString).listFiles();
            DateFormat localDateFormat = com.estrongs.android.pop.h.a().K();
            SimpleDateFormat localSimpleDateFormat = new SimpleDateFormat(" hh:mm:ss a");
            paramString = "[\r\n";
            i = 0;
            if (i < localObject.length)
            {
              paramString = paramString + "{";
              paramString = paramString + "\"name\":\"" + localObject[i].getName() + "\", ";//文件名
              String str = paramString + "\"time\":\"" + localDateFormat.format(new Date(localObject[i].lastModified())) + localSimpleDateFormat.format(new Date(localObject[i].lastModified())) + "\", ";//时间
              if (!localObject[i].isDirectory()) {
                break label437;
              }
              paramString = "folder";
              paramString = str + "\"type\":\"" + paramString + "\", ";//文件类型
              paramString = paramString + "\"size\":\"" + a(localObject[i].length()) + "\", ";//大小
              if (i == localObject.length - 1)
              {
                paramString = paramString + "}\r\n";
                break label430;
              }
              paramString = paramString + "}, \r\n";
              break label430;
            }
            paramString = paramString + "]";
            localObject = new c.b(this, "200 OK", "text/plain", a(paramString));
            ((c.b)localObject).a("Content-Length", "" + paramString.getBytes("utf-8").length);
            return localObject;
          }
          catch (Exception paramString)//错误处理
          {
            paramString.printStackTrace();
            return new c.b(this, "500 Internal Server Error", "text/plain", paramString.toString());
          }
          return new c.b(this, "400 Bad Request", "text/plain", "Not Supported");
          label430:
          i += 1;
          continue;
          label437:
          paramString = "file";
        }
      }

    当获取的commandappLaunch时,会调用a()方法执行APP,运行成功会返回HTTP CODE 200,不存在则返回500并返回not found the package信息

    public c.b a(JSONObject paramJSONObject)
      {
        for (;;)
        {
          try
          {
            paramJSONObject = paramJSONObject.getString("appPackageName");
            if (paramJSONObject != null)
            {
              new Intent();
              paramJSONObject = FexApplication.c().getPackageManager().getLaunchIntentForPackage(paramJSONObject);
              i = 0;
              if (paramJSONObject != null)
              {
                FexApplication.c().startActivity(paramJSONObject);
                paramJSONObject = "{" + "\"result\":\"" + i + "\"";
                paramJSONObject = paramJSONObject + "}";
                c.b localb = new c.b(this, "200 OK", "text/plain", a(paramJSONObject));
                localb.a("Content-Length", "" + paramJSONObject.getBytes("utf-8").length);
                return localb;
              }
            }
            else
            {
              paramJSONObject = new c.b(this, "500 Internal Server Error", "text/plain", "not found the package " + paramJSONObject);
              return paramJSONObject;
            }
          }
          catch (Exception paramJSONObject)
          {
            paramJSONObject.printStackTrace();
            return new c.b(this, "500 Internal Server Error", "text/plain", paramJSONObject.toString());
          }
          int i = -1;
        }

    当获取到的commandappPull时,会调用C方法下载一个指定文件,如果存在则返回200并且以数据流传输回设备中,否则返回500并回显not found the package,代码如下

      public c.b c(JSONObject paramJSONObject)
      {
        try
        {
          Object localObject = paramJSONObject.getString("appPackageName");
          if (localObject != null)
          {
            paramJSONObject = new File(FexApplication.c().getPackageManager().getApplicationInfo((String)localObject, 0).sourceDir);
            if (paramJSONObject.exists())
            {
              localObject = new c.b(this, "200 OK", "application/octet-stream", new FileInputStream(paramJSONObject));
              ((c.b)localObject).a("Content-Length", "" + paramJSONObject.length());
              return localObject;
            }
          }
          paramJSONObject = new c.b(this, "500 Internal Server Error", "text/plain", "not found the package " + (String)localObject);
          return paramJSONObject;
        }
        catch (Exception paramJSONObject)
        {
          paramJSONObject.printStackTrace();
        }
        return new c.b(this, "500 Internal Server Error", "text/plain", paramJSONObject.toString());
      }

    当传入的commandlistAppslistAppsSystemlistAppsPhonelistAppsSdcardlistAppsAll时,会调用a(int paramInt)方法执行相应功能,a方法代码如下

    public c.b a(int paramInt)
      {
        Object localObject2;
        PackageInfo localPackageInfo;
        Object localObject3;
        for (;;)
        {
          try
          {
            Object localObject1 = FexApplication.a().b(8192);
            localObject2 = new LinkedList();
            localObject1 = ((List)localObject1).iterator();
            if (!((Iterator)localObject1).hasNext()) {
              break;
            }
            localPackageInfo = (PackageInfo)((Iterator)localObject1).next();
            localObject3 = localPackageInfo.applicationInfo;
            if (paramInt == 0)//传入a的等于0
            {
              if (((((ApplicationInfo)localObject3).flags & 0x80) == 0) && ((((ApplicationInfo)localObject3).flags & 0x1) != 0)) {
                continue;
              }
              ((LinkedList)localObject2).add(localPackageInfo);
              continue;
            }
            if (paramInt != 1) {
              break label146;
            }
          }
          catch (Exception localException)
          {
            localException.printStackTrace();
            return new c.b(this, "500 Internal Server Error", "text/plain", localException.toString());
          }
          if ((((ApplicationInfo)localObject3).flags & 0x1) > 0)
          {
            ((LinkedList)localObject2).add(localPackageInfo);
            continue;
            label146:
            if (paramInt == 2)//传入a的值等于2
            {
              if ((((ApplicationInfo)localObject3).flags & 0x40000) == 0) {
                ((LinkedList)localObject2).add(localPackageInfo);
              }
            }
            else if (paramInt == 3)//传入a的值等于3
            {
              if ((((ApplicationInfo)localObject3).flags & 0x40000) != 0) {
                ((LinkedList)localObject2).add(localPackageInfo);
              }
            }
            else if (paramInt == 4) {//传入a的等于4
              ((LinkedList)localObject2).add(localPackageInfo);
            }
          }
        }
        for (;;)
        {
          String str;
          if (paramInt < ((LinkedList)localObject2).size())
          {
            localPackageInfo = (PackageInfo)((LinkedList)localObject2).get(paramInt);
            localObject3 = new File(localPackageInfo.applicationInfo.sourceDir);
            if (!((File)localObject3).exists()) {}
            for (long l = 0L;; l = ((File)localObject3).length())
            {
                                    //回显JSON的字符串进行拼接
              str = localException + "{";
              str = str + "\"packageName\":\"" + localPackageInfo.packageName + "\", ";
              str = str + "\"label\":\"" + com.estrongs.android.pop.utils.c.a(FexApplication.a().getPackageManager(), localPackageInfo.applicationInfo) + "\", ";
              str = str + "\"version\":\"" + localPackageInfo.versionName + "\", ";
              str = str + "\"versionCode\":\"" + localPackageInfo.versionCode + "\", ";
              localObject3 = localPackageInfo.applicationInfo.sourceDir;
              str = str + "\"location\":\"" + (String)localObject3 + "\", ";
              str = str + "\"size\":\"" + l + "\", ";
              str = str + "\"status\":\"" + localPackageInfo.applicationInfo.backupAgentName + "\", ";
              str = str + "\"mTime\":\"" + localPackageInfo.lastUpdateTime + "\"";
              if (paramInt >= ((LinkedList)localObject2).size() - 1) {
                break;
              }
              str = str + "},\r\n";
              break label760;
            }
            str = str + "}\r\n";
          }
          else
          {
            str = str + "]";
            localObject2 = new c.b(this, "200 OK", "text/plain", a(str));
            ((c.b)localObject2).a("Content-Length", "" + str.getBytes("utf-8").length);
            return localObject2;
            str = "[\r\n";
            paramInt = 0;
            continue;
          }
          label760:
          paramInt += 1;
        }
      }

    总结

    漏洞主要成因是官方没有对文件分享功能的传入功能的传入者进行身份合法性验证,导致可以被任意执行命令,第一次做APP分析,不足之处请多多指出

    不傲不畏,不卑不亢,不骄不躁,不气不馁,不争不抢
    那么快就实现了。。
    使用道具 举报 回复
    发表于 2019-1-21 08:56:46
    学习!!
    使用道具 举报 回复
    前排支持大佬
    使用道具 举报 回复
    分析app就很厉害了。
    使用道具 举报 回复
    发表于 2019-1-22 13:35:46
    自己一直对于APP的漏洞复现不怎么多,学习了
    使用道具 举报 回复
    发表于 2019-1-25 10:22:21
    看了一下代码,不过还是没搞清楚“从打开那个启动Activity到在59777端口开启HTTP服务这个过程的代码在哪里”。求知道的大佬指教[抱拳]
    使用道具 举报 回复
    发表于 2019-1-25 10:29:54
    icq27aa52e1 发表于 2019-1-25 02:22
    看了一下代码,不过还是没搞清楚“从打开那个启动Activity到在59777端口开启HTTP服务这个过程的代码在哪里 ...

    这个暂时没找到相关包,但是在popAudioPlayer这个包里面有检测是否来自127.0.0.1:59777这个地址,可能在这里吧
    不傲不畏,不卑不亢,不骄不躁,不气不馁,不争不抢
    使用道具 举报 回复
    发表于 2019-1-25 10:53:55
    Sp4ce 发表于 2019-1-25 10:29
    这个暂时没找到相关包,但是在popAudioPlayer这个包里面有检测是否来自127.0.0.1:59777这个地址,可能在 ...

    我也看到那个AudioPlayer了,但是找了很久都没能找到真正开启HTTP服务的地方。只看到调用HTTP响应的地方,和判断HTTP请求参数的地方。
    使用道具 举报 回复
    发表于 2019-1-25 14:58:03
    好像在这里:com.estrongs.android.f.a.java
    微信图片_20190125145350.png
    微信图片_20190125145411.png
    其中a继承自c。
    com.estrongs.android.f.c.java
    微信图片_20190125145415.png

    使用道具 举报 回复
    发表于 2019-1-25 15:41:38
    icq27aa52e1 发表于 2019-1-25 06:58
    好像在这里:com.estrongs.android.f.a.java

    nice job
    不傲不畏,不卑不亢,不骄不躁,不气不馁,不争不抢
    使用道具 举报 回复
    发表于 2019-1-29 10:43:09
    鼓励转贴优秀软件安全工具和文档!
    使用道具 举报 回复
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册