根据权威数据分析:
那么,这个市场到底有多大,目前,根据媒体披露,美团外卖2015年上半年业绩:42.5亿。如果按照1/4市场计算,那么,外卖市场有200亿RMB。 怪不得BAT要抢占这块市场,当然分析用户的饮食习惯也是他们的另外一个目的。
然后,我们再看看中国外卖O2O鼻祖饿了么融资情况。
2015年8月份占市场份额为38.75%,累计投资大约10亿美金。
那么,我们关心的是,这么大笔投资,真正使用在IT基础设施建设上面和业务系统搭建的投入是多少?业务系统搭建中,具体的安全投入是多少?如果按照涉及到IT基础设施业务系统搭建占比1%(总投资额计算),安全投入咱以上IT投资的1%的话,那就是安全相当10万美金的投入。
但是,如果你是一个极客,当然是想通过客户端漏洞用Scrapy 爬出大约每天多少单,每单赚多少钱。存储到Mongodb中,然后使用python脚本写大数据报告,用数据说话麽,大家都信,呵呵。
那么基于业务系统App安全评估如何具体做呢?下面着重描述一下。
其实网上有很多方法论的东西,这里我站在自由测评人的角度上分析。
首先确定只对Android App APK进行分析,因为Android系统比较开放,模拟器比较多,业务逻辑ipa和Apk是一样的。
在网络上在线评估网站比比皆是,其实其App漏洞评估平台技术都差不多。
一个Android程序上传后首先要判断其是否可以反编译。如果可以,那么就能分析到更多的数据,包括java写的源代码。
具体流程图如下:
当然,如果反静态编译失败,就需要动态分析的方法。动态分析技术是对应用软件安装、运行过程的行为监测和分析。检测的方式使用虚拟机方式通过建立与Android手机终端软件运行环境几乎一样的虚拟执行环境,手机应用软件在其中独立运行,从外界观察应用程序的执行过程和动态,进而记录应用程序可能表现出来的恶意行为。
最后,在通过虚拟机分析线上App程序的时候,可能会遇到模拟器检查、root检测等或者IMEI标识判断等高级防调试的功能,这时候就需要人工去分析,也就是说在真实Android手机上安装App进行实地的安全评估。
那么,下面我们以到家美食汇App作为安全评估对象进行评估:
测试方法使用含有Apktool开源软件的发编译IDE工具测试,Android killer/ ApkIDE改之理 。本文使用Android killer。
从这张图可以看出apk被编译成smali(java虚拟机文件)然后又被反编译成Java class的源代码。
通过java Decompiler查看源代码发现:
2.1、内置组件:
Com.alibaba.fastjson 阿里巴巴开源高性能JSON开发包 com.android.volley Android官方通信框架Volley com.nostra13.universalimageloader 开源 图片库加载。 com.sina.sso 新浪微博登陆SSO组件 com.squareup.okhttp 高效http客户端 (网络拥堵) okio 开源基本工具库
使用开源组件包,好处是可以提高开发效率,但是如果是开源组件安全性令人担忧,但是目前为止漏洞库中还没有以上组件安全问题。这时不时的让我想起来去年的AFNetworking组件当时引发的中间人攻击的案例。
2.2、第三方SDK组件
com.baidu.mapapi 百度地图 com.baidu.android.pushservice 百度消息推送服务 com.google.analytics google分析API让我们知道用户如何与我们的应用进行交互 com.tencent 访问腾讯openapi 认证等功能 com.umeng.analytics 应用统计分析
那么,外包第三方组件主要搜索重点是APPKey等,可能被黑客利用:
<meta-data android:name="UMENG_APPKEY" android:value="528187ac56240bee3803bc39" /> <meta-data android:name="UMENG_CHANNEL" android:value="daojiaweb" />
百度推送:可以获取到推送服务key值以及推送消息等信息,通过修改源码,使开发商利益受到损失。
<service android:name="com.baidu.android.pushservice.CommandService" android:exported="true" /> <meta-data android:name="api_key" android:value="2qoGQfsfg5CcB9cU9PXHNBGX" />
<meta-data android:name="com.baidu.lbsapi.API_KEY" android:value="jGY5Ft6EuDv9gKWT9dg51UOl" />
3.1、SharedPreferences
首先检查的是SharedPreferences,这是一个可以全局访问的数据结构。经过源码查找发现。
但是,到家使用了混淆类处理:ObscuredSharedPreferences,这个类有对SharedPreferences的加解密函数,但是在源代码被反编译后,没有任何保护力度,黑客可以直接拿解密函数破解其加密数据
public class ObscuredSharedPreferences implements SharedPreferences { private static final char[] SEKRIT = { 23, 45, 79, 65, 98, 21, 13, 18, 64, 28 }; protected static final String UTF8 = "utf-8"; protected Context context; protected SharedPreferences delegate; protected String decrypt(String paramString) { if (paramString != null) {} for (;;) { try { arrayOfByte = Base64.decode(paramString, 0); SecretKey localSecretKey = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(new PBEKeySpec(SEKRIT)); // 使用java相关加密算法解密,秘钥是SEKRIT Cipher localCipher = Cipher.getInstance("PBEWithMD5AndDES"); localCipher.init(2, localSecretKey, new PBEParameterSpec(Settings.Secure.getString(this.context.getContentResolver(), "android_id").getBytes("utf-8"), 20)); //UTF-8编码 return new String(localCipher.doFinal(arrayOfByte), "utf-8"); } catch (Exception localException) { byte[] arrayOfByte; throw new RuntimeException(localException); } arrayOfByte = new byte[0]; } } protected String encrypt(String paramString) { if (paramString != null) {} for (;;) { try { arrayOfByte = paramString.getBytes("utf-8"); SecretKey localSecretKey = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(new PBEKeySpec(SEKRIT)); // 使用java相关加密算法加密,秘钥是SEKRIT Cipher localCipher = Cipher.getInstance("PBEWithMD5AndDES"); localCipher.init(1, localSecretKey, new PBEParameterSpec(Settings.Secure.getString(this.context.getContentResolver(), "android_id").getBytes("utf-8"), 20)); //UTF-8编码 return new String(Base64.encode(localCipher.doFinal(arrayOfByte), 2), "utf-8"); } catch (Exception localException) { byte[] arrayOfByte; throw new RuntimeException(localException); } arrayOfByte = new byte[0]; } }
3.2、ContentProvider
ContentProvider是android组件之一,可以提供数据的跨应用程序访问,针对其搜索关键字为:ContentResolver。因为这个函数是解析ContentProvider存储的数据的。
但是里面没有什么有价值的数据,pass.
3.3、sqlite
所有的Android数据存储都是使用轻量级的sqlite数据库,在源码中有很多地方都能找到,我就不一一列举了。 同样也没什么有价值的数据 pass.
4.1、敏感信息检测>>获取设备信息
该app中使用了获取设备信息的权限,窃取用户隐私。
DaojiaApp: { Utils.initSomeThing(); //获取GPS数据 Globals.instance().currentVersion = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; //获取App程序版本 Globals.instance().manufacturer = Build.MANUFACTURER; //Build.MANUFACTURER // 硬件制造商 Globals.instance().model = Build.MODEL; // Build.MODEL // 版本 Globals.instance().os_version = Build.VERSION.RELEASE; //获取android系统的版本信息->版本字符串 Build.VERSION.RELEASE WifiInfo localWifiInfo = ((WifiManager)getSystemService("wifi")).getConnectionInfo(); Globals.instance().mac = localWifiInfo.getMacAddress(); //获取wifi MAC地址 TelephonyManager localTelephonyManager = (TelephonyManager)getSystemService("phone"); //获得手机号 Globals.instance().imei = localTelephonyManager.getDeviceId(); //获取IMEI DisplayMetrics localDisplayMetrics = new DisplayMetrics(); //屏幕大小 getWindowManager().getDefaultDisplay().getMetrics(localDisplayMetrics); Globals.instance().w = localDisplayMetrics.widthPixels; Globals.instance().h = localDisplayMetrics.heightPixels; Globals.instance().resolution = (localDisplayMetrics.widthPixels + "_" + localDisplayMetrics.heightPixels); ObscuredSharedPreferences localObscuredSharedPreferences = new ObscuredSharedPreferences(this, getSharedPreferences("daojia", 0)); if (localObscuredSharedPreferences.getInt("CityID", 0) != 0) { Globals.instance().cityID = localObscuredSharedPreferences.getInt("CityID", 0); }
4.2、敏感信息检测>>App服务器地址
后来通过http抓包发现,这个地址只是一个获取服务器地址的中间环节,真正的服务器地址是在这个地址中存储,并且检测其连接App的属性。
4.3、敏感信息检测>>字符串初始化检测
//针对服务器提交的翻译命令 public static final String DELETEHISTORYADDRESSREQUEST = "DeleteHistoryAddress"; public static final String DELETEHISTORYORDERREQUEST = "DeleteHistoryOrder"; public static final String DOACTIVEUSER = "DoActiveUser"; public static final String DOCOMPLAINTREQUEST = "DoComplaint"; public static final String DOLOGINREQUEST = "DoLogin"; public static final String DOLOGOUT = "DoLogout"; public static final String DOREGISTERREQUEST = "DoRegister"; public static final String DOREGISTERREQUESTRESP = "DoRegisterResp"; public static final String DOURGENTREQUEST = "DoUrgent"; public static final String GETADVERTISELISTREQUEST = "GetAdvertiseList"; public static final String GETAREAANNOUNCEMENTLISTREQUEST = "GetAreaAnnouncementList"; public static final String GETAREALISTREQUEST = "GetAreaList"; public static final String GETAUTHORIZATIONCODEREQUEST = "GetAuthorizationCode"; public static final String GETCARDLISTSREQUEST = "GetCardList"; public static final String GETCITYLISTREQUEST = "GetCityList"; public static final String GETCOUPONLISTREQUEST = "GetCouponList"; public static final String GETFOODCATAGORYLISTREQUEST = "GetFoodCatagoryList"; public static final String GETFOODLISTREQUEST = "GetFoodList"; public static final String GETHISTORYADDRESSREQUEST = "GetHistoryAddress"; public static final String GETHISTORYORDERLISTREQUEST = "GetHistoryOrderList"; public static final String GETHISTORYRESTAURANTLISTREQUEST = "GetHistoryRestaurantList"; public static final String GETHOTFOODLISTREQUEST = "GetHotFoodList"; public static final String GETORDERDETAILSREQUEST = "GetOrderDetails"; public static final String GETPROFILEREQUEST = "GetProfile"; public static final String GETPROFILEREQUESTRESP = "GetProfileResp"; public static final String GETRESTAURANTCATAGORYLISTREQUEST = "GetRestaurantCatagoryList"; public static final String GETRESTAURANTLISTREQUEST = "GetRestaurantList"; public static final String GETRESTAURANTMESSAGELISTREQUEST = "GetRestaurantMessageList"; public static final String GETTODAYORDERLISTREQUEST = "GetTodayOrderList";
4.4、敏感信息检测>>密码管理
登陆密码保存使用ObscuredSharedPreferences同样的数据密码加密形式。
private void doLogin() { if (Upgrade.hasUpgrade(this.frameActivity)) { return; } new Server(this.frameActivity, getResources().getString(2131034313)) { protected Integer doInBackground(String… paramAnonymousVarArgs) { ArrayList localArrayList1 = new ArrayList(); localArrayList1.add("DoLogin"); int i = Globals.instance().interactWithServer(localArrayList1, null, null); if (i != 0) { return Integer.valueOf(i); } ArrayList localArrayList2 = new ArrayList(); localArrayList2.add("GetHistoryAddress"); localArrayList2.add("GetProfile"); localArrayList2.add("GetCardList"); return Integer.valueOf(Globals.instance().interactWithServer(localArrayList2, null, null)); } protected void onPostExecute(Integer paramAnonymousInteger) { super.onPostExecute(paramAnonymousInteger); if (paramAnonymousInteger.intValue() != 0) { Globals.instance().isLogined = false; SharedPreferences.Editor localEditor = DaojiaApplication.getInstance().getSharedPreferences("token", 0).edit(); localEditor.putString("terminalToken", ""); localEditor.commit(); EditText localEditText1 = Login.this.accountEditText; Globals.instance().mobile = ""; localEditText1.setText(""); EditText localEditText2 = Login.this.passwordEditText; Globals.instance().passwd = ""; localEditText2.setText(""); new AlertDialog.Builder(Login.this.frameActivity).setMessage(Globals.instance().error(paramAnonymousInteger.intValue(), Login.this.frameActivity.getResources())).setNegativeButton(Login.this.frameActivity.getResources().getString(2131034125), null).show().setCanceledOnTouchOutside(false); return; } ObscuredSharedPreferences localObscuredSharedPreferences = new ObscuredSharedPreferences(Login.this.frameActivity, Login.this.frameActivity.getSharedPreferences("daojia", 0)); localObscuredSharedPreferences.edit().putString("Account", Globals.instance().mobile).commit(); localObscuredSharedPreferences.edit().putString("Passwd", Globals.instance().passwd).commit(); //直接使用ObscuredSharedPreferences 加密方式处理 Globals.instance().isStroll = false; int i = Globals.instance().todayOrderTotal; if (i != 0) { Login.this.frameActivity.tabActivity.showBadge(1, i); } for (;;) { EasyTracker localEasyTracker = EasyTracker.getInstance(Login.this.frameActivity); localEasyTracker.set("&cd", "登录成功"); localEasyTracker.set(Fields.customDimension(1), "Yes"); localEasyTracker.send(MapBuilder.createAppView().build()); try { Login.this.frameActivity.getSupportFragmentManager().popBackStack(); return; } catch (Exception localException) { Login.this.isBack = true; return; } Login.this.frameActivity.tabActivity.hideBadge(1); } } }.execute(new String[] { "" }); }
测试方法,在Android手机,安装到家美食汇App,然后手机上安装Fiddler证书,设置wifi代理获取http(s)数据流量
5.1、登录数据分析
Json值完全没加密
建议在使用工具分析HTTP(s)的数据时,如果可以反编译java源代码的话,就可以更直观的了解其整个登录流程。
由于登录源代码在以上密码分析部分已经说过,这次看注册函数:
在daojia.fragment.Register 下,onClick动作下面。
同时,还发现在输入内容上没有做任何过滤,有SQL injection的风险。
public void onClick(View paramView) { if (paramView.getId() == 2131492865) { localView = this.frameActivity.getWindow().getCurrentFocus(); if (localView != null) { ((InputMethodManager)this.frameActivity.getSystemService("input_method")).hideSoftInputFromWindow(localView.getWindowToken(), 0); } this.frameActivity.getSupportFragmentManager().popBackStack(); } while (paramView.getId() != 2131492906) { View localView; return; } if ((this.linkman1EditText.getText().toString().trim().length() == 0) && (this.linkman2EditText.getText().toString().trim().length() == 0)) { new AlertDialog.Builder(this.frameActivity).setMessage(getResources().getString(2131034159)).setPositiveButton(getResources().getString(2131034125), null).show().setCanceledOnTouchOutside(false); return; } if (this.mobileEditText.length() == 0) { new AlertDialog.Builder(this.frameActivity).setMessage(getResources().getString(2131034160)).setPositiveButton(getResources().getString(2131034125), null).show().setCanceledOnTouchOutside(false); return; } if (!this.mobileEditText.getText().toString().matches("^1[3,4,5,8,7]//d{9}$")) { new AlertDialog.Builder(this.frameActivity).setMessage(getResources().getString(2131034161)).setPositiveButton(getResources().getString(2131034125), null).show().setCanceledOnTouchOutside(false); return; } if (this.passwdEditText.length() == 0) { new AlertDialog.Builder(this.frameActivity).setMessage(getResources().getString(2131034162)).setPositiveButton(getResources().getString(2131034125), null).show().setCanceledOnTouchOutside(false); return; } if (this.passwdEditText.length() < 6) { new AlertDialog.Builder(this.frameActivity).setMessage(getResources().getString(2131034163)).setPositiveButton(getResources().getString(2131034125), null).show().setCanceledOnTouchOutside(false); return; } if (this.passwdEditText.getText().toString().compareTo(this.confirmEditText.getText().toString()) != 0) { new AlertDialog.Builder(this.frameActivity).setMessage(getResources().getString(2131034164)).setPositiveButton(getResources().getString(2131034125), null).show().setCanceledOnTouchOutside(false); return; } MobclickAgent.onEvent(this.frameActivity, "Step2ForRegistration"); Log.e("main", "step2ForRegistration"); Globals.instance().mobile = this.mobileEditText.getText().toString(); doGetCode(); } public View onCreateView(LayoutInflater paramLayoutInflater, ViewGroup paramViewGroup, Bundle paramBundle) { super.onCreateView(paramLayoutInflater, paramViewGroup, paramBundle); this.rootLayout = ((ViewGroup)paramLayoutInflater.inflate(2130903096, paramViewGroup, false)); this.linkman1EditText = ((EditText)this.rootLayout.findViewById(2131492984)); this.linkman1EditText.addTextChangedListener(this); this.linkman2EditText = ((EditText)this.rootLayout.findViewById(2131492985)); this.linkman2EditText.addTextChangedListener(this); ((InputMethodManager)this.frameActivity.getSystemService("input_method")).toggleSoftInput(2, 0); this.mobileEditText = ((EditText)this.rootLayout.findViewById(2131493014)); this.passwdEditText = ((EditText)this.rootLayout.findViewById(2131492994)); this.confirmEditText = ((EditText)this.rootLayout.findViewById(2131493035)); ((TextView)this.rootLayout.findViewById(2131492866)).setText(2131034143); ImageView localImageView = (ImageView)this.rootLayout.findViewById(2131492865); localImageView.setVisibility(0); localImageView.setOnClickListener(this); ((Button)this.rootLayout.findViewById(2131492906)).setOnClickListener(this); return this.rootLayout; }
5.2、下单业务流程
Json没加密是不是就可以刷单了,或者篡改数据了?
5.3、重放攻击
这个问题,先在这里说明一下,如果要抵制App重放攻击,需要在每个请求包总添加包序列号,每次提交自动加一,如果收到的包相同就认为是重放攻击。目前到家美食汇App是做到的,虽然没有后台的源程序,但是从客户端源代码(com.daojia.ds)中可以发现:
private int getUrl() { Object localObject1 = ""; for (;;) { int j; try { JSONObject localJSONObject1 = new JSONObject(); try { localJSONObject1.put("Command", "LookupServer"); localJSONObject1.put("SequenceID", "0"); localJSONObject1.put("CheckDigit", "0"); JSONObject localJSONObject3 = new JSONObject(); localJSONObject3.put("CityID", this.cityID); localJSONObject1.put("Body", localJSONObject3); JSONArray localJSONArray3 = new JSONArray(); localJSONArray3.put(localJSONObject1); String str3 = localJSONArray3.toString(); localObject1 = str3; } catch (JSONException localJSONException) { *** continue; } localHttpPost = new HttpPost(DAOJIASERVER); Log.e("afei", "request body is ===" + (String)localObject1); localStringEntity = new StringEntity((String)localObject1, "UTF-8"); localStringEntity.setContentType("application/x-www-form-urlencoded"); localHttpPost.setEntity(localStringEntity); localHttpPost.addHeader("Accept-Encoding", "gzip, deflate"); List localList = ((BasicCookieStore)this.localContext.getAttribute("http.cookie-store")).getCookies(); bool = localList.isEmpty(); int i = 0; int k; StringBuilder localStringBuilder; String str2; JSONArray localJSONArray1; if (!bool) { k = 0; if (k < localList.size()) {} } else { if (i == 0) { str1 = DaojiaApplication.getInstance().getSharedPreferences("token", 0).getString("terminalToken", ""); if (!TextUtils.isEmpty(str1)) { localHttpPost.addHeader("Cookie", "token=" + str1 + ";"); } } localHttpResponse = this.httpClient.execute(localHttpPost, this.localContext); localHttpEntity = localHttpResponse.getEntity(); localObject2 = localHttpResponse.getEntity().getContent(); localHeader = localHttpResponse.getFirstHeader("Content-Encoding"); if ((localHeader != null) && (localHeader.getValue().equalsIgnoreCase("gzip"))) { localObject2 = new GZIPInputStream((InputStream)localObject2); } localInputStreamReader = new InputStreamReader((InputStream)localObject2); localBufferedReader = new BufferedReader(localInputStreamReader, 8192); localStringBuilder = new StringBuilder(); str2 = localBufferedReader.readLine(); if (str2 != null) { continue; } localHttpEntity.consumeContent(); localJSONArray1 = new JSONArray(localStringBuilder.toString()); j = 0; if (j < localJSONArray1.length()) { continue; } return -1; } if (((Cookie)localList.get(k)).toString().contains("token")) { i = 1; break label640; localStringBuilder.append(str2); continue; } JSONObject localJSONObject2; JSONArray localJSONArray2; k++; } catch (Exception localException) { *** } localJSONArray2 = localJSONObject2.getJSONArray("Servers"); Globals.instance().host0 = localJSONArray2.optString(0, ""); Globals.instance().host1 = localJSONArray2.optString(1, ""); Globals.instance().mapping = localJSONObject2.getString("Mapping"); if (getCityV(Globals.instance().mapping) == 0) { LOOKUPS = Globals.instance().host0; } else { LOOKUPS = Globals.instance().host1; } } catch (ConnectTimeoutException localConnectTimeoutException) { return -1; } label640: continue; label646: j++; } }
有关 App SQL注入的攻击测试方法可以参考
原理:就是用burpsuite 设置代理记录log文件,然后,狂点App上的界面,然后通过提交的post数据,进行sqlmap测试。
sqlmap -r "/root/desktop/daojia.txt" --threads=3 --risk=3 --level=3 --random-agent --time-sec=15 --timeout=15 --beep –dbs
但是经过测试目前还没有注入漏洞。
1.发送者的身份认证:由于开发商可能通过使用相同的 Package Name 来混淆替换已经安装的程序,以此保证签名不同的包不被替换。2.保证信息传输的完整性:签名对于包中的每个文件进行处理,以此确保包中内容不被替换。
PackageInfo packageInfo = getPackageManager().getPackageInfo( "com.daojia.xxx", PackageManager.GET_SIGNATURES); Signature[] signs = packageInfo.signatures;
使用关键字“PackageInfo”搜索。Signature 太多,第三方和开源组件基本上都有签名校验。
主程序没有做签名校验,使用resign工具可以直接打包。。
最后,还是向大家介绍一下online检测工具。具有检测内容我就不在文章中列举了。大家可以自己上传看看,星多漏洞多。
这里想向大家详细介绍一下Mobile security Framework,这个用python写的开源的评估程序,这个工具加快了App程序漏洞评估速度,当然,自动化的程序特别是开源的很容易被App厂商和谐掉,还是要使用手工分析方法。或者是使用自动化分析工具先看看有什么详细漏洞,然后使用手工分析。
个人觉得MobSF在建立动态分析环境方面很快,本人通过翻墙才下载到ova虚拟化文件(oracle Virtualbox)。实现了动态分析,其实就是一个google android手机操作系统模拟器,通过python把各个手工测试工具连接起来做成自动化分析程序。
经过检测发现美团外卖做的相对比较好,到家美食汇做的相对差一点。我想这和在安全上的投入有关系,在残酷的市场竞争环境下,首先要解决生存的问题,然后才能考虑安全的问题,当然了,如果你是互联网巨头BAT也会有先天的优势,安全性的研究会有N多NB人才立助你业务系统安全,对付开源检测工具还是绰绰有余的。那么对于创业掌门人的你,在App业务系统安全性问题上,你会选择传统互联网安全公司(爱加密、梆梆等)对你的业务系统做企业级的加固,还是建立自己SRC团队做安全运营?或者采用众测的形式花钱买漏洞?