My last blog post was like 1 year old so I thought I would quickly write something interesting with a bug I found recently on Twitter (Periscope). The bug (CSRF) itself is not particuarly impressive but the way I approached to it and my thought process should hopefully be worth sharing.
Someday ago I noticed Twitter, or rather Periscope had published the so-called Producer API that is only available for its partners. The use case for the API is for certain third party applications (e.g. external camera devices) to live stream videos directly to a Periscope account.
This sounds like something to be done with OAuth and from my past experience OAuth implementation is often vulnerable. Therefore I decided to take a look into it.
The first problem I encountered was there's not even a public documentation for the API. They included a list of partners using the API in the blog post so I thought I could try to see how they interact with the API and maybe test something out of it. Unfortunately most of them require a subscription in order to use the Periscope feature, and even if I were willing to pay, it wouldn't help because those are web applications and the server is handling everything.
I then noticed there's one mobile application (Mevo on iOS) also using the API. In mobile applications, OAuth requests are often issued directly from the client side and it's possible to intercept the traffic to understand the API calls. Unlike OAuth 1.0a which uses a signature to prevent interception to hide important information like consume_secret, OAuth 2.0 solely relies on having all traffic on HTTPS. Therefore, unless the application is using some tough certificate pinning technique (which most of them by the way, can be solved with SSL Kill Switch 2) there should not be a problem.
The second problem here was that in order to even start using the app, you need to have a Mevo camera. The best price from Amazon was $399.99 USD so I didn't bother to buy one just to test something that might not even be vulnerable.
So, I tried to do something I was not familiar with, reverse engineering. To do this, I prepared a jailbroken iPhone with Clutch to decrypt the IPA file, class-dump to generate the Objective-C header files and Hopper to disassemble the codes.
I started by searching the word "Periscope" from the header files because it's very likely they have classes with that name which handles the logic of interacting with Periscope.
Some file names like PeriscopeBroadcastCreateOperation.h
and PeriscopeBroadcastPublishAPIOperation.h
caught my attention as they look related to API calls to Periscope. By observing these files, I noticed they all extend the class PeriscopeAPIOperation
, so that might be the next thing to look at.
//
// Generated by class-dump 3.5 (64 bit).
//
// class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard.
//
#import "GroupOperation.h"
@class NSDictionary, NSMutableURLRequest, PeriscopeOAuthOperation, PeriscopeRefreshTokenAPIOperation, URLSessionTaskOperation;
@interface PeriscopeAPIOperation : GroupOperation
{
NSDictionary *_JSON;
URLSessionTaskOperation *_taskOperation;
PeriscopeRefreshTokenAPIOperation *_refreshTokenOperation;
PeriscopeOAuthOperation *_oauthOperation;
NSMutableURLRequest *_request;
}
+ (void)removeCookies;
+ (void)logout;
+ (id)userID;
+ (id)refreshToken;
+ (id)accessToken;
+ (void)updateAccessToken:(id)arg1;
+ (void)setAccessToken:(id)arg1 refreshToken:(id)arg2 forAccount:(id)arg3;
+ (_Bool)isUserAuthorized;
+ (id)buildRequestForPath:(id)arg1 params:(id)arg2 query:(id)arg3 queryItems:(id)arg4 HTTPMethod:(id)arg5 accessToken:(id)arg6;
@property(retain, nonatomic) NSMutableURLRequest *request; // @synthesize request=_request;
@property(retain, nonatomic) PeriscopeOAuthOperation *oauthOperation; // @synthesize oauthOperation=_oauthOperation;
@property(retain, nonatomic) PeriscopeRefreshTokenAPIOperation *refreshTokenOperation; // @synthesize refreshTokenOperation=_refreshTokenOperation;
@property(retain, nonatomic) URLSessionTaskOperation *taskOperation; // @synthesize taskOperation=_taskOperation;
@property(retain) NSDictionary *JSON; // @synthesize JSON=_JSON;
- (void).cxx_destruct;
- (void)repeatOperation;
- (_Bool)operationHas401Code;
- (_Bool)shouldHandle401Code;
- (void)operationDidFinish:(id)arg1 withErrors:(id)arg2;
- (void)finishWithError:(id)arg1;
- (id)initWitMethod:(id)arg1 params:(id)arg2;
- (id)initWitMethod:(id)arg1 params:(id)arg2 HTTPMethod:(id)arg3;
- (id)initWitMethod:(id)arg1 queryItems:(id)arg2;
- (id)initWitMethod:(id)arg1 params:(id)arg2 HTTPMethod:(id)arg3 queryItems:(id)arg4;
@end
Woah that's quite a lot of properties and methods. Looking at these method names, this class should be responsible to handle the API calls to Periscope.
I opened Hopper to test my theory and sure enough, they call the initWitMethod
method to initiate an API call with parameters.
From there, I just needed to find out what other places are calling this method and I could have a list of Periscope API calls and the parameter names. By furthing analyzing the class, I also extracted the API root, client_id and client_secret of Mevo from the static strings.
After all the hassle, I could finally start checking vunlerabilities of Periscope's OAuth implementation. I quickly noticed the initial authorization endpoint did not have CSRF protection. As a third party Periscope application can request full permissions on a user's Periscope account, it is possible that an attacker can prepare a malicious third party application, and have users authorizing it unknowingly to perform actions on the users' behalf.
The concrete details which are less interesting about the bug can be found in my report listed below.
Conclusion
- I keep an eye on Twitter's updates as I believe this makes sure I can test new features before anybody else.
- Besides, features that are only open to certain parties don't mean you can't test it before it's public, you just need to find a way to access them. Bottom line, you can kindly ask the program if it allows you to access the features for testing (although, Twitter hasn't responded to my request).
- Last but not least, don't quit right away when something requires you to pay. Even if you can't find a way to bypass the payment as I did, it's a good call to pay anyway because people generally don't want to invest on uncertainty, which means opportunity to explore areas nobody has looked at. I have heard success from people following this approach.