本文原创作者:bt0sea
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)验证服务器端对输入校验。
(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('0');
}
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每次登陆都一样,那么,完全可以撞库。
设置8次提交。JSON数据返回值都一样({"msg":"手机号或密码错误!","status":13007}),那证明,服务器端java应用程序没有次数限制判断。
那么,具体怎么撞库,请参考:
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
提交的时候可以提交User-Agent:等相关参数为移动App提交,如果服务器端有相关的判断,可以绕过。
同时,建议通过wireshark观察一下返回值的情况。
经过测试发现没有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的手机上。