简单分析下CVE-2019-6447
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
步骤
安装ES文件管理器,版本低于4.1.9.7.4并启动,不执行任何操作
接入与电脑同一局域网
对指定设备进行扫描nmap -sS -p 1-65535 -v 192.168.2.63
验证POC 执行命令python3 poc.py --cmd getDeviceInfo --network 192.168.2.
回显设备信息和FTP端口号 复现成功
漏洞分析 从一些渠道下载到了低于官网的软件安装包,然后解包后将3个classes.dex转为jar 最终目录图如下
用JD-GUI打开JAR包
对POC中的关键字如59777
、command
等进行全局搜索,最终取得执行漏洞的关键代码和,具体如下
漏洞包关键代码 开启服务器相关代码 public static boolean a (boolean paramBoolean) { boolean bool1 = true ; synchronized (g) { if ((f != null ) && (f.d())) { return true ; } a locala = f; if (locala == null ) {} } try { f.i(); f = null ; int i = 0 ; for (;;) { if (i < 5 ) { try { f = new a ("/sdcard" , 59777 + i, paramBoolean); int j = 1000 ; while (j > 0 ) { boolean bool2 = f.d(); if (bool2) { paramBoolean = bool1; return paramBoolean; localObject2 = finally ; throw localObject2; } Thread.sleep(200L ); j -= 200 ; } } catch (Exception localException1) { f = null ; localException1.printStackTrace(); i += 1 ; } } } return false ; } catch (Exception localException2) { for (;;) { continue ; paramBoolean = false ; } } }
其中a继承自c。
public c (int paramInt) { this .b = paramInt; this .l = new ServerSocket (this .b); this .c = new Thread (new Runnable () { public void run () { try { while (!c.a(c.this )) { new c .a(c.this , c.b(c.this ).accept()); } return ; } catch (IOException localIOException) {} } }); this .c.setDaemon(true ); this .c.start(); }
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" )) { localObject = new String (g()); try { localObject = new JSONObject ((String)localObject); String str = ((JSONObject)localObject).getString("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" )) { return a(3 ); } if (str.equals("listAppsAll" )) { return a(4 ); } if (str.equals("getAppThumbnail" )) { return d((JSONObject)localObject); } if (str.equals("appLaunch" )) { return a((JSONObject)localObject); } if (str.equals("appPull" )) { 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 ; } }
由于漏洞应用条件是在同一个局域网下,所以应用在获取完传入的方法【指令】后调用com.estrongs.android.util.af
中的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 ); }
并且同时调用comestrongsandroidutilad
中的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(); }
当传入的command
为listFiles
时,会调用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.getName() + "\", " ; String str = paramString + "\"time\":\"" + localDateFormat.format(new Date (localObject.lastModified())) + localSimpleDateFormat.format(new Date (localObject.lastModified())) + "\", " ; if (!localObject.isDirectory()) { break label437; } paramString = "folder" ; paramString = str + "\"type\":\"" + paramString + "\", " ; paramString = paramString + "\"size\":\"" + a(localObject.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" ; } }
当获取的command
为appLaunch
时,会调用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 ; }
当获取到的command
为appPull
时,会调用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()); }
当传入的command
为listApps
、listAppsSystem
、listAppsPhone
、listAppsSdcard
、listAppsAll
时,会调用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 ) { 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 ) { if ((((ApplicationInfo)localObject3).flags & 0x40000 ) == 0 ) { ((LinkedList)localObject2).add(localPackageInfo); } } else if (paramInt == 3 ) { if ((((ApplicationInfo)localObject3).flags & 0x40000 ) != 0 ) { ((LinkedList)localObject2).add(localPackageInfo); } } else if (paramInt == 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()) { 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分析,不足之处请多多指出