Abusing Twitter API

This article is an up-to-date version of a research I first presented at App Sec Forum Western Switzerland 2012 and then at Hack in the Box 2013 (Amsterdam).

This article is not intended to be formal, but more to be used as a technical reference for details that were not explained in the slides.

[2013-04-11] PCWorld article

[2013-10-16] I presented a followup as a rump session at AppSec Forum 2013 (slides).

[2013-10-24] I presented a session on "iOS / Twitter Integration" at SoftShake 2013 (slides) which was less focused on security and more on software development.

[2014-05-22] I presented the STTwitter library at CocoaHeads Lausanne, May 2014 (slides, custom Twitter conversation on a dedicated account).

[2014-05-22] I presented the STTwitter library at CocoaHeads Lausanne, May 2014 (slides, custom Twitter conversation on a dedicated account).

[2014-07-02] I created this visual documentation of Twitter API.

Session Description

Twitter has decided that his web APIs would require signed requests by March 2013 [1]. Hence Twitter will be able to block any third-party application which fails to meet the new very strict terms of service [2][3].

Although this decision can be justified by commercial reasons, it is disputable from a technical stand point. We intend to demonstrate how a Twitter client can abuse or circumvent the OAuth authentication. Such a client would then be free to ignore Twitter ToS. He can remove promoted tweets and display the messages the way he wants, without Twitter being able to do anything about it.

[1] https://dev.twitter.com/blog/changes-coming-to-twitter-api
[2] https://dev.twitter.com/terms/api-terms
[3] https://dev.twitter.com/terms/display-guidelines

Table of Contents

  1. The Old and the New Twitter
  2. Twitter and OAuth
    2.1 PIN-based Authentication
    2.2 xAuth Authentication
    2.3 Application Only Authentication
    2.4 Reverse Authentication
  3. Extracting Consumer Tokens
    3.1 /usr/bin/strings
    3.2 GDB
    3.3 Memory Dump
    3.4 DTrace Probes
    3.5 Code Injection
  4. Finding Undocumented API Endpoints
    4.1 Reverse Engineering Twitter.app
    4.2 Promoted Content
    4.3 Accounts Creation
    4.4 Hardcoded Tokens
  5. Twitter on OS X and iOS
    5.1 Twitter APIs on OS X / iOS Integration
    5.2 STTwitter, an Objective-C Library for Twitter
    5.3 TwitHunter, a Twitter Client over STTwitter
  6. Security Considerations
    6.1 Reaching application limits
    6.2 Bearer token invalidation
    6.3 Keys revocation
    6.4 OAuth Session fixation attack
    6.5 Sending passwords over the network
    6.6 Fighting spam
    6.7 Keys protection
    6.8 Incorrect, downgraded OAuth implementation
  7. Conclusion

1. The Old and the New Twitter

Twitter used to be a young, open, developer-friendly platform.

Anyone could download the tweets, or post a tweet providing a username and a password.

Early Twitter users somehow invented or at least made @reply and #hashtags popular.

A rich ecosystem was built around Twitter API.

Gradually, Twitter decided it was getting a grown-up company and should start monetizing its user base.

Instead of selling premium subscriptions to users, Twitter chose to sell ads to advertisers.

This strategic choice was supposed to:

  1. please advertisers and consumers by building a consistent, pervasive, controlled platform
  2. preventing third-party developer from hiding ads, if writing third-party clients at all

To implement these measures, Twitter has recently added strong constraints on the users of their API, eg:

Twitter can block access to specific applications if these guidelines are not applied.

Moreover, the rules are quite impossible to comply with and change all the time.

Additionaly, standard third-party applications cannot exceed 100,000 users.

Twitter openly tries to discourage third-party Twitter clients:

"Developers ask us if they should build client apps that mimic or reproduce the mainstream Twitter consumer client experience. The answer is no." "We need to move to a less fragmented world, where every user can experience Twitter in a consistent way."

@rsarver (Ryan Sarver, Director, Platform at Twitter) https://groups.google.com/forum/#!msg/twitter-development-talk/yCzVnHqHIWo/sC34r_ZyMLYJ

Of course, these moves are poorly accepted by long-time Twitter users and applications developers:

"Twitter obviously wants to make money by advertising in the stream. This will be impossible if all of the mechanisms aren't implemented to spec within a client. They need full control of how the information is presented, and do not have the bandwidth to micromanage ads with third parties to prevent fraud, poor presentation, etc,"

Ollie Wagner (Twittelator, a third party client) http://www.theverge.com/2012/7/9/3135406/twitter-api-open-closed-facebook-walled-garden

Several companies simply stopped developing for Twitter clients while some of them are trying to play the game.

Some power users are already leaving Twitter for other micro-blogging platforms such as app.net. In september this year, Twitter removing the RSS feeds only accelerated this move.

The purpose of this article is to explain how Twitter uses OAuth to identify the client application, show how to impersonate other client applications and discuss this situation from an application security standpoint.

So, we first need to study in detail the way Twitter requests are built up.

2. Twitter and OAuth

Historically, Twitter API did use HTTP Basic Authentication. In December 2009, Twitter introduced OAuth authentication and deprecated Basic Authentication. OAuth has been mandatory since September 2010.

OAuth is an standard authentication protocol over HTTP which allows users to approve or refuse applications to act on their behalf, without sharing their password.

OAuth 1.0 is described in RFC 5849. Although OAuth 2.0 is ready, Twitter uses version 1.0a.

So how does it all work? Well, instead of sending a username and password, OAuth uses "access tokens", two values which will be used to build every API request.

Let's take a sample query. This query posts the message "hello #asfws":

POST https://api.twitter.com/1.1/statuses/update.json

HEADERS
     Authorization = OAuth oauth_consumer_key="7YBPrscvh0RIThrWYVeGg", \
                           oauth_nonce="B916DA1D-5163-404E-BBFD-6B23610A", \
                           oauth_signature_method="HMAC-SHA1", \
                           oauth_timestamp="1350986996", \
                           oauth_version="1.0", \
                           oauth_token="15111995-MYAoxeiGNxwYbckY76v6JBgH8qVdEdKsFf5MXkfFX", \
                           oauth_signature="569AfY3o2hh%2B7aNC%2FuI7rx%2FZ73c%3D"

POST DATA
     status = hello%20%23asfws

oauth_consumer_key is the "consumer key" for Instagram
oauth_nonce is a random, unique string
oauth_signature_method is always "HMAC-SHA1"
oauth_timestamp is the epoch timestamp of the request
oauth_version is always "1.0"
oauth_token is the "access key"
oauth_signature is HMAC_SHA1(request_parameters, signing_key)

signing_key is consumer_secret&access_secret

So there are "consumer" tokens to authenticate the client application.

And there are "access" tokens, to authenticate the user.

Every time, a "key" token will be sent over the network, and a "secret" token won't be sent, but will be used to sign the request instead.

So OAuth is used to:

It is to be noted that OAuth does not ensure request confidentiality, since it is not required to use SSL.

2.1 PIN-based Authentication

Before setting up a user account, a client application only has consumer tokens, which are part of the application.

When the users sets up a user account in its Twitter client, the application will start a process which will end up with the reception of access tokens.

These access tokens will enable the user to use Twitter with his client application, as seen with the sample query.

The normal workflow is a three step process.

1) The application asks Twitter for request tokens and gets, as a response, a URL that contains the request tokens.

-> POST https://api.twitter.com/oauth/request_token

    (signed with consumer_secret)

<- https://api.twitter.com/oauth/authorize    
    ? oauth_token              = 5mLVRwABK47EwuX3vCsEqW9QEGdgIP4qL75UPYpdcc
    & oauth_token_secret       = hmeCdVB48lpC35q8fTUJ3SIegVB9uSPdWxL6WbLvgY
    & oauth_callback_confirmed = true

2) The application then asks the user to authorize the request tokens on Twitter website, and receives a PIN.

-> GET https://api.twitter.com/oauth/authorize
    ? oauth_token              = 5mLVRwABK47EwuX3vCsEqW9QEGdgIP4qL75UPYpdcc
    & oauth_token_secret       = hmeCdVB48lpC35q8fTUJ3SIegVB9uSPdWxL6WbLvgY
    & oauth_callback_confirmed = true

    (signed with consumer_secret)

[user authenticates on Twitter website]
[user authorizes the application]

<- 5728738

3) The application sends Twitter the PIN and receives the access tokens.

-> POST https://api.twitter.com/oauth/access_token
    oauth_verifier = 5728738

    (signed with consumerSecret)

<- oauth_token        = 15111995-dOmQRJgYJHA18FGEsrC2iWyxTbi12YoooOwOPCRd0
   oauth_token_secret = xFEMGp6pMKLL0by2ngGby23B1bhgwhKt2go0s1uKY

The first digits of oauth_token stand for the Twitter user ID.

Twitter has now associated the application and the user.

It also has generated and returned access tokens valid for the application and the user.

All subsequent API requests are signed with OAuth as seen before, the signing key being consumer_secret&access_token_secret, eg. when retrieving the timeline:

-> GET https://api.twitter.com/1.1/statuses/home_timeline.json

    (signed with consumer_secret&access_token_secret)

<- twitter timeline in json format

Access tokens can be revoked by the user at anytime by revoking the client application. Note that changing the password of a Twitter account does not revoke access tokens.

This PIN-based authentication process is quite complicated. It needs opening a browser, takes time and can be confusing for the user.

Fortunately, not all Twitter clients have to go through it. For instance, the official iOS Twitter client doesn't need a PIN. It can retrieve OAuth tokens directly from a username and password. This happens through a xAuth request.

2.2 xAuth Authentication

Compared to the PIN-based authentication, xAuth skips the first two steps and directly goes to the "access token" step. The exchange of request tokenS for access tokens is modified to provide the username and password instead of the PIN.

-> POST https://api.twitter.com/oauth/access_token
    x_auth_mode = client_auth
    x_auth_username = nst021
    x_auth_password = xxxxxxxx

    (signed with consumer_secret)

<- oauth_token        = 15111995-eR4Ii5TxvT3lnNHLhmpmMzWxi7BMldQKhQNRxAgRA
   oauth_token_secret = FdxiqQSmvjm7hiSXWQRItTUksIAtHpDAFXDSk6Q8c

Not all consumer tokens are xAuth enabled. Developers can have their application be aAuth enabled by writing to api@twitter.com and explaining why their application needs this xAuth capability.

2.3 Application Only Authentication

In March 2013, Twitter introduced Application Only authentication which can use the API without user context. Hence, there is no need to authenticate the user, but only the application. API endpoints such as POST statuses/update will return an error, whereas GET /1.1/statuses/user_timeline.json will work as expected.

These requests follow the Client Credentials Grant flow of the OAuth 2 specification. The requests don't have to be signed with OAuth, they simply have to send a bearer token in the HTTP Authorization header:

-> GET /1.1/statuses/user_timeline.json?count=100&screen_name=twitterapi
    Authorization: Bearer XXXXXXXX

In order to receive a bearer token, the application sends a POST request using consumer_key:consumer_secret encoded in base64 as a Basic authorization value:

-> POST /oauth2/token
    Authorization: Basic YYYYYYYY

    grant_type=client_credentials

<- {"token_type":"bearer","access_token":"XXXXXXXX"}

When requested multiple times with the same consumer key and secret, the same bearer token in returned.

This bearer token can also be invalidated:

-> POST /oauth2/invalidate_token
Authorization: Basic YYYYYYYY

access_token=XXXXXXXX

<- {"access_token":"XXXXXXXX"}

2.4 Reverse Authentication

Reverse Auth is yet another variant of OAuth. Given a user U logged in with an application APP1, Reverse Auth allows APP1 to enable APP2 to retrieve access tokens valid for U and APP2.

Phase 1: login with APP2, obtain a special request token in the form of an OAuth header

-> GET https://api.twitter.com/oauth/request_token

<- OAUTH_HEADER

Phase 2: login with APP1, obtain the access tokens

-> POST https://api.twitter.com/oauth/access_token
x_reverse_auth_target = APP2.CONSUMER_KEY
x_reverse_auth_parameters = OAUTH_HEADER

<- a string containing access tokens for the user on APP2

A typical use case is an iOS application requesting access to the default iOS Twitter account in order to authenticate the user on a remote service and access his Twitter account.

iOS/OSX     Twitter      Server
|------------->|              | reverse auth. phase 1 (APP2)
|< - - - - - - |              | oauth header
|              |              |
|------------->|              | reverse auth. phase 2 (APP1 is Twitter on iOS or OS X)
|< - - - - - - |              | access tokens
|              |              |
|---------------------------->| access tokens
|              |              |       
|              |<-------------| access Twitter on user's behalf with APP2
|              | - - - - - - >|

Note that phase 1 does not need data from APP1. It means that this phase can be performed on an iOS device but also anywhere else. Performing phase 1 from a remote server is a good practice since it avoids shipping the APP2 consumer secret along with an iOS application.

Also, when APP1 is Twitter for iOS, APP2 can access the user's direct messages, contrary to what can be read here and there. iOS users who grant an application access to their Twitter account are not always aware of that.

3. Extracting Consumer Tokens

We just saw that, for each kind of authentication, client applications need consumer tokens, and that consumer tokens represent the client identity since they authenticate the client with Twitter.

If we can find consumer tokens from real clients, then we know how to send Twitter API requests looking exactly the same as the one from the real clients and perform any of these authentication while impersonating the original client.

So how do we find these keys? Sniffing the network would only reveal consumer_key but not consumer_secret, which is part of the signing key and is not sent over the wire.

Let's see how to extract consumer keys from popular Twitter clients, including OS X and iOS.

3.1 /usr/bin/strings

Consumer tokens can be simply embedded in the binary code, waiting to be dumped, such as with Twitter official client for OS X, Twitterrific or Tweetbot.

Twitter.app

$ strings /Applications/Twitter.app/Contents/MacOS/Twitter

3rJOl1ODzm9yZy63FACdg # consumer_key
5jPo************************************** # consumer_secret

Twitterrific

$ strings /Applications/Twitterrific.app/Contents/MacOS/Twitterrific

ZSdaBDXCKZ9kLPe4Ymr0Q
Tq66***************************************

Tweetbot.app

$ strings /Applications/Tweetbot.app/Contents/MacOS/Tweetbot

HS47qOdHzVFQYXTJMA
qvkE***************************************

We can quickly test the tokens for validity with the Tweepy Python module:

#!/usr/bin/env python

import tweepy # sudo easy_install tweepy

CONSUMER_KEY = '3rJOl1ODzm9yZy63FACdg'
CONSUMER_SECRET = '5jPo**************************************'

auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth_url = auth.get_authorization_url()
print "Please authorize:", auth_url
verifier = raw_input('PIN: ').strip()
auth.get_access_token(verifier)

print "ACCESS_KEY:", auth.access_token.key
print "ACCESS_SECRET:", auth.access_token.secret

3.2 GDB

Several other applications try to protect their consumer tokens with some level of obsfuscation. The strings command does not reveal the tokens. However, examining method names with GDB (info func OAuth, info func consumer, ...) can reveal the names of the Objective-C which return the consumer tokens. We can then set breakpoints, interact with the application so that it will need the consumer tokens, and when breakpoints are hit, we just let the functions finish and return. At this point, all we have to do is to read the return value of the function. This value is stored in the $eax register for Intel 32-bits and the $rax register for Intel 64-bits.

This technique works fine to retrieve consumer tokens from Socialite but also from OS X Twitter integration and iOS Twitter integration. In this last case, we attache GDB to a process running in the iPhone simulator.

Socialite (64 bits)

$ gdb /Applications/Socialite.app/Contents/MacOS/Socialite

(gdb) info func OAuth
(gdb) fb -[EBOAuth consumerKey]
(gdb) fb -[EBOAuth consumerSecret]
(gdb) run

Breakpoint 1, 0x000000010037080c in -[EBOAuth consumerKey] ()
(gdb) finish
(gdb) po $rax
fjnGL2oVUrkKYusrAgvrKg

Breakpoint 2, 0x000000010037083f in -[EBOAuth consumerSecret] ()
(gdb) finish
(gdb) po $rax
LtOv**************************************

OS X (64 bits)

$ gdb attach <PID of /System/Library/Frameworks/Accounts.framework/Versions/A/Support/accountsd>

(gdb) fb -[OACredential consumerKey]
(gdb) finish
(gdb) po $rax
tXvOrlJDmLnTfiUqJ3Kuw

(gdb) fb -[OACredential consumerSecret]
(gdb) finish
(gdb) po $rax
AWcB**************************************

iOS 6 Simulator (32 bits)

$ gdb attach <PID of /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator6.0.sdk/System/Library/Frameworks/Accounts.framework/accountsd>

(gdb) fb -[OACredential consumerKey]
(gdb) finish
(gdb) po (int*)$eax
WXZE9QillkIZpTANgLNT9g

(gdb) fb -[OACredential consumerSecret]
(gdb) finish
(gdb) po (int*)$eax
Aau5**************************************

iOS 7 Simulator uses the same values.

3.3 Memory Dump

Some other applications hide their symbols. It gets difficult to know the address of the functions that return the consumer tokens. However, we know that the program will need to use the tokens at some point because they are needed to sign OAuth requests.

We can then use another technique which consists in dumping the memory of the process and looking for the tokens into the dump.

We will use the gcore tool from Amit Singh's book "Mac OS X Internals": http://www.osxbook.com/book/bonus/chapter8/core/.

Let us use this technique to retrieve the consumer tokens from YoruFukurou.

YoruFukurou

Dump the process memory.

$ sudo ./gcore64 -c /tmp/dump.bin 4149

The file will be quite huge. Before using strings on the file, we must remove or alter the Mach-O magic header 0xCFFAEDFE or strings won't run.

$ printf '\x00\x00\x00\x00' | dd conv=notrunc of=/tmp/dump.bin

We can then extract unique strings:

$ strings dump.bin | sort -u > /tmp/dump.txt

We can start by looking for consumer_key. The consumer key appears in all HTTP headers of the OAuth requests. Note that this value is not really hidden and could have been retrieved by sniffing the network.

$ grep "consumer" /tmp/dump.txt
(...)&oauth_consumer_key=WfEZ02WzcqZMvs4HJMZLA(...)

Conversely, the consumer_secret is the real meat we are looking for.

We know from Twitter documentation on signature creation that consumer_secret is part of the signing key, separated from access_token_secret with an ampersand, ie HMAC_signing_key == consumer_secret&token_secret. Additionaly, before the application receives access tokens, OAuth requests are signed with consumer_secret only. Now it's easy to discover the consumer_secret by grepping this pattern.

$ egrep "[a-zA-Z0-9]{20}$" /tmp/dump.txt

69zI**************************************

3.4 DTrace Probes

Yet another technique consists in logging the freed pointers with a DTrace probe:

$ sudo dtrace -n 'pid$target::free:entry { printf("%s", arg0 != NULL ? copyinstr(arg0) : "<NULL>"); }' -p 10123

The output is pretty verbose but can be reduced with the same sort -u technique as above, and grepped accordingly.

3.5 Code Injection

A more Objective-C variant of logging every freed pointer would be logging only the deallocated NSString instances. This technique consists in writing a NSString category with a my_dealloc method logging the string just before calling [super dealloc]. This method must then be swizzled with the regular dealloc method.

@implementation NSString (ST)

+ (void)load {
    Swizzle([NSString class], @selector(dealloc), @selector(my_dealloc));
}

- (void)my_dealloc {
    NSLog(@"%@", self);
    [self my_dealloc];
}

@end

The code can then be compiled in a framework and dynamically loaded with GDB.

(gdb) p (char)[[NSBundle bundleWithPath:@"/Library/Frameworks/LogNSStringDealloc.framework"] load]

3.6 Googling for other keys

We've seen how to extract tokens "the hard way", but anyone can find dozens of valid consumer tokens with Google, ie..:

https://gist.github.com/re4k/3878505
http://www.binrand.com/post/2399484-twitter-ipad-consumer-key.html
http://rndc.or.id/wiki/index.php/(Ab)Using_Twitter_Client
https://github.com/mitsuhiko/logbook/blob/master/twitter-secrets.txt
...

4. Finding Undocumented API Endpoints

Twitter REST API version 1.1 is documented on https://dev.twitter.com/docs/api/1.1 streaming APIs on https://dev.twitter.com/docs/streaming-apis. The API may receive minor changes from time to time. In order to remain up to date, follow the developer blog, @twitterapi recently updated documentation and the calendar of API changes.

The interesting thing is that all API endpoints are not documented. Several of them are used by official Twitter clients and not available to third-party clients.

Check out my visual documentation of Twitter API for all documented as well as private API endpoints I could find.

4.1 Reverse Engineering Twitter.app

Most if not all endpoints can be found by dumping strings from the official Twitter client.

GET activity/about_me.json
GET activity/by_friends.json
GET conversation/show.json
GET discover/highlight.json
GET discover/universal.json
GET statuses/:id/activity/summary.json
GET statuses/media_timeline.json
GET statuses/mentions_timeline.json
GET timeline/home.json
GET trends/available.json
GET users/recommendations.json

If you are familiar with Twitter API, you should be able to guess most parameters easily, but you may also want to check the actual parameters sent to these endpoints. To do that, you can open the Twitter binary with Hopper.app, browse methods names and notice the address of somewhere you'd like to break, eg. 0x00028582 meth_ABHTTPRequest_setParameters_:. Your gdb session will look similar to this for Twitter.app version 2.3.1:

$ /usr/bin/gdb /Applications/Twitter.app/Contents/MacOS/Twitter --arch i386
(gdb) b *0x00028582    
(gdb) r

(gdb) # check where we are by printing self and _cmd
(gdb) po *(int*)($ebp+8)
TwitterAPI
(gdb) p (char *) *(int*)($ebp+12)
$12 = 0xf8b3c "baseRequestWithPartialURL:parameters:apiRoot:"

(gdb) # print 'partialURL'
(gdb) po *(int*)($ebp+16)
activity/about_me.json

(gdb) # print 'parameters'
(gdb) po *(int*)($ebp+20)
{
    "contributor_details" = 1;
    "include_entities" = true;
    "include_my_retweet" = true;
    "since_id" = 1378200219000;
}

(gdb) # print 'apiRoot'
(gdb) po *(int*)($ebp+24)
https://api.twitter.com/1.1

A much more efficient technique to discover the actual endpoints and their parameters is sniffing the network. Unfortunately, setting up an HTTPS proxy is not sufficient because official Twitter clients use SSL certificate pinning. It means that the applications will refuse to establish a connection if the certificate presented by the server does not match the one hardcoded in the application. There are several techniques to defeat certificate pinning. We will use Hopper to decompile Twitter.app and patch the binary to bypass certificate pinning on a jailbroken device.

Let's take Twitter for iPad version 5.11.1 and open it in Hopper. By browsing the disassembled code, we quickly find the check that won't let the application connect to api.twitter.com. In -[ABHTTPRequest connection:willSendRequestForAuthenticationChallenge:] are some instructions meaning:

if([self _isPinnedCertificateChain:chain]) {
    // goto the happy path ie. 0x258c0c
} else {
    // create an error and return
}

At 0x00260cf0 is the very test that will decide what to do according to the result of the _isPinnedCertificateChain: message send. The result, originally stored in r0, was moved into r4 at 0x00258ae0. Now tst.w evaluates r4 & 0xFF and sets the Z flag if the result is zero. Then, the bne instruction tests the Z flag and, if clear, jump to 0x260e14. In summary, we can read these two lines as "if r4 is true then branch else go on" (to error creation).

0x00260cd4 45F2EA50    movw     r0, #0x55ea
0x00260cd8 3246        mov      r2, r6
0x00260cda C0F23900    movt     r0, #0x39
0x00260cde 7844        add      r0, pc       ; 0x5f62cc
0x00260ce0 0168        ldr      r1, [r0]     ; @selector(_isPinnedCertificateChain:)
0x00260ce2 2046        mov      r0, r4
0x00260ce4 B0F1DEEC    blx      imp___picsymbolstub4__objc_msgSend
0x00260ce8 0446        mov      r4, r0
0x00260cea 2846        mov      r0, r5
0x00260cec B0F1FAEC    blx      imp___picsymbolstub4__objc_release
0x00260cf0 14F0FF0F    tst.w    r4, #0xff    ; Z flag = (r4 == 0)
0x00260cf4 40F08E80    bne.w    0x260e14     ; branch if Z flag is not set (r4 != 0)

In order to take the happy path in any case, we will replace the test tst.w r4, 0xff with the unconditional branch b 0x260e14. In order to find the opcodes for this unconditional branch, we can use the Hopper menu "Assemble Instruction" which gives 90E0.

- 0x00260cf0 14F0FF0F  tst.w    r4, #0xff
+ 0x00260cf0 90E0      b        0x260e14    ; jump to the happy path
+ 0x00260cf2           db       0xff        ; unused
+ 0x00260cf3           db       0x0f        ; unused

Note that, if you don't have Hopper or if you want to understand where 90E0 comes from, you can calculate this value by yourself with the ARM THUMB Instruction Set. The opcode format for unconditional branch is 1110 0--- ---- ---- where dashes are a 11-bits offset shifted to the right. We want to jump from 0x00260cf4 to 0x00260e14 ie an offset of 0x120 or 0000 0001 0010 0000. Shift this value to the right and add the unconditional branch format, you'll end up with 1110 0000 1001 0000 == 0xE090 or 0x90E0 in little-endian representation.

Yes another way to find the 90E0 opcode is using llvm-mc:

$ echo "b 0x120" | llvm-mc -assemble -triple=thumbv7 -show-encoding
.text
b   #288                    @ encoding: [0x90,0xe0]

So, changing 2 bytes on nearly 12 millions was enough to bypass this annoying security feature. Now the modified Twitter application will establish connections with any SSL/TLS certificate trusted by a trusted CA and we can happily observe the HTTPS traffic in mitmproxy.

4.2 Promoted Content

Here is for example how Twitter.app for iPad does fetch the timeline. The statuses/home_timeline.json endpoint is documented but the exact parameters are not:

GET https://api.twitter.com/1.1/statuses/home_timeline.json?cards_platform=iPad-3&contributor_details=1&count=20&earned=true&include_cards=1&include_entities=1&include_my_retweet=1&include_user_entities=true&pc=true&send_error_codes=1

The response may contain promoted content because of the pc=true parameter. Promoted tweets have an additional attribute named "promoted_content" which looks like:

"promoted_content" = {
    advertiser =             {
        description = "Follow us here for all the news on Chevrolet in Europe";
        name = "Chevrolet Europe";
        // ...
    };
    "disclosure_type" = promoted;
    "impression_id" = 1ffa1774327be2ec;
    // ...
}

And now it gets easy to edit the iOS binary, change the pc string into, say, xx, resign it with your own certificate, deploy it OTA or copy it through XCode and your Twitter.app won't display ads anymore :-) Step by step recipe:

1) on jailbroken device, decrypt the Twitter binary (or find it somewhere on the web)

# Clutch Twitter

2) download the decrypted .ipa on your Mac

$ scp root@192.168.178.29:/var/root/Documents/Cracked/Twitter-v5.11.1.ipa .
$ unzip Twitter-v5.11.1.ipa
$ cd Payload/Twitter.app

3) with Hopper, read Twitter and locate the pc string used in requests, change it into xx

$ printf 'xx' | dd bs=1 seek=4710234 conv=notrunc of=Twitter

4) on iOS dev. portal, create a provisioning profile for app ID com.atebits.*

5) sign the modified Twitter.app with your developement certificate

$ export CODESIGN_ALLOCATE=/usr/bin/codesign_allocate
$ codesign -fs "iPhone Developer: Nicolas Seriot" Twitter

6) in XCode Organizer, drag and drop your modified Twitter.app on your device

4.3 Accounts Creation

Here is how accounts are created from iOS Settings on iOS 5 and iOS 7:

POST https://api.twitter.com/1/account/generate.json

Authorization header (notice the consumer key):

Authorization:     OAuth oauth_nonce="C4E16213-9058-49E8-A06E-65A5D961EED0", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1378598935", oauth_consumer_key="IHUYavQ7mmPBhNiBBlF9Q", oauth_token="8285392-niqOtDvwwUXOzQJsCvDxcPndUBHb4dWrTLXw1nTw", oauth_signature="V6ySPsviDz%2BJnTvBFoE2qpHJv70%3D", oauth_version="1.0"

Parameters:

adc:                    pad
discoverable_by_email:  0
email:                  EMAIL
geo_enabled:            0
lang:                   en
name:                   NAME
password:               PASSWORD
screen_name:            SCREEN_NAME
send_error_codes:       true
time_zone:              CEST

This request will fail using any other consumer key but IHUYavQ7mmPBhNiBBlF9Q.

You can find the related consumer secret as well as access tokens by opening an Twitter account from iOS 7 Simulator with gdb attached to /Applications/Preferences.app/Preferences.

This is very similar to what we did on 3.2. First set breakpoints on consumerKey and consumerSecret getters:

(gdb) info fun consumer
...
0x057ad644  -[OACredential consumerKey]
0x057ad6ad  -[OACredential consumerSecret]
...
(gdb) b *0x057ad644
(gdb) b *0x057ad6ad

Then, open an account from the Settings. When breakpoints are hit, go the the end of the getter function and read the return value:

Breakpoint 1, 0x057ad644 in -[OACredential consumerKey] ()
(gdb) finish
Run till exit from #0  0x057ad644 in -[OACredential consumerKey] ()
(gdb) po $eax
IHUYavQ7mmPBhNiBBlF9Q

Breakpoint 2, 0x057ad6ad in -[OACredential consumerSecret] ()
(gdb) finish
Run till exit from #0  0x057ad6ad in -[OACredential consumerSecret] ()
(gdb) po $eax
cIBZ*************************************

This consumerSecret has been published by @dll7 in February 2013 on Twitter.

Hence, creating Twitter accounts can be scripted. No need to fill captchas anymore.

A fun fact with these consumer tokens is the identity shown in the authorize tokens phase: "iOS5 SignUp, mehack.com, Test sign-up application".

Another fun fact is that the oauth token 8285392-niqOtDvwwUXOzQJsCvDxcPndUBHb4dWrTLXw1nTw which appears in the signup request from iOS 5 belongs to userID 8285392. This account is @raffi (Raffi Krikorian, VP Platform Engineering at Twitter).

So basically, when creating an account, iOS 5 uses "iOS 5 Signup" consumer tokens and @raffi user account. And @raffi secret token is hardcoded in iOS 5. Great! (see next section for details)

On iOS 7.1 the account used to sign signup requests is 179654598 ie @twobiledev which seems a bit more reasonable.

4.4 Hardcoded Tokens

Access tokens and consumer tokens are hardcoded, sometimes lightly obfuscated. Here are their locations. Also, meet Twitter super secret tokens obfuscation: substracting 1 to odd-indexed characters.

Twitter Consumer Tokens

iOS 5.1
/System/Library/Frameworks/Twitter.framework/Twitter
iOS 6 and iOS 7
/System/Library/Accounts/Authentication/TwitterAuthenticationPlugin.bundle/TwitterAuthenticationPlugin

# consumer key (obfuscated then clear)
XX[E:QjlmkJZqTBNhLOT:g
WXZE9QillkIZpTANgLNT9g

# consumer secret (obfuscated then clear, redacted)
Bav5TVOpCZd0XCrn8DuQrMGZbkHivaFYBnsbz3HUJE
Aau***************************************

OS X 10.9
/System/Library/Accounts/Authentication/TwitterAuthenticationPlugin.bundle/Contents/MacOS/TwitterAuthenticationPlugin

# consumer key (obfuscated then clear)
uXwOslKDnLoTgiVqK3Lux
tXvOrlJDmLnTfiUqJ3Kuw

# consumer secret (obfuscated then clear, redacted)
BWdBT1CYR3EROKOgzZyeyXMjW6eLxc8K7EJ4QlBqIE
AWcB**************************************

Signup Consumer Tokens and related Access Tokens

iOS 5 and iOS 6
/System/Library/PreferenceBundles/TwitterSettings.bundle/TwitterSettings

# signup consumer key, in clear
IHUYavQ7mmPBhNiBBlF9Q

# signup consumer secret, in clear, readacted
cIBZ*************************************

# signup access token, in clear
8285392-niqOtDvwwUXOzQJsCvDxcPndUBHb4dWrTLXw1nTw

# signup access secret, in clear, redacted
YRa0**************************************

iOS 7
/System/Library/PreferenceBundles/TwitterSettings.bundle/TwitterSettings

# consumer key (obfuscated then clear)
JHVYbvR7nmQBiNjBClG9R
IHUYavQ7mmPBhNiBBlF9Q

# consumer secret (obfuscated then clear, redacted)
dICZU9O7gMso5kyZb5K2tGWoqLUwr9NnCDzvRXOuN
cIBZ*************************************

# access token (obfuscated then clear)
27:664699-zuld[L[cEfyU6P[B[dKcDpbJG5cKVJUdyXpxVZ:u
179654598-yukdZLZcDfxU5PZBZdJcCpaJF5bKUJTdxXoxUZ9u

# access token secret (obfuscated then clear, redacted)
ZEiwuMgSMV3yxNyNPTcuvEO3iEkf8Gsk76w5F6:Mbo
YEhw**************************************

Here is a Python's one-liner that will help:

print ''.join(["%c" % (ord(c) - 1) if i % 2 == 0 else c for i, c in enumerate(s)])

These tokens are all you need to craft an OAuth request to /account/generate.json.

5. Twitter on OS X and iOS

5.1 Twitter APIs on OS X / iOS Integration

Since iOS 5 and OS X 10.8, users can setup a Twitter account in the System Preferences. This account can then be accessed by any application after asking for user's permission through Objective-C APIs, namely Accounts.framework and Social.framework.

All the requests are then signed with iOS or OS X consumer secret. Can Twitter then identify (and revoke) the application making a certain request. WWDC 2011 session 124 (from 36:26) says:

"As part of this signing process, we actually embed enough information about your process that Twitter can identify your application correctly and attribute tweets that come from you on the Twitter web site so you won't lose that identification.".

Indeed, the application ID is sent along with every request in the application_id parameters, along with an adc parameter, probably identifying the device. For example:

adc=pad
application_id=ch.seriot.MyApp

This is only true with TWRequest instances (iOS 5). Note that this value cannot be overridden. Adding your own application_id parameter will result in two parameters like application_id=sdf&application_id=ch.seriot.MyApp.

Since iOS 6, you can use SLRequest instances and, from what I could observe, the application_id parameter is not sent anymore, and so Twitter cannot tell which application is using iOS Social.framework.

5.2 STTwitter, an Objective-C Library for Twitter

As an alternative to OS X Twitter integration, applications can use their own Twitter and OAuth library. There are several of them, one of the best ones being OAuthConsumer.

I still find it a bit outdated (no block based API) and cumbersome to use for my taste. To be sure to understand the in-and-outs of Twitter OAuth authentication, I've decided to write my own library and created STTwitter. In fact, STTwitter is a Twitter-only OAuth 1.0a library, but also provides an interface for OS X Twitter integration.

STTwitter sources are available on GitHub: https://github.com/nst/STTwitter.

A development version is available at http://seriot.ch/resources/abusing_twitter_api/STTwitter.app.zip.

Please refer to this (presentation) at SoftShake 2013 for more details.

5.3 TwitHunter, a Twitter Client over STTwitter

To ensure that the STTwitter library is actually usable, I integrated it into TwitHunter.

TwitHunter has been my historical Twitter client pet project. It never was fully functional by let me experiment ideas like scoring, which consists in calculating a score for each tweet according to a set of user-defined rules, and then filtering out the tweets below a certain score.

TwitHunter is now also a proof of concept of a Twitter client where the user can choose the client identity he wants. It is somewhat similar to a browser where you can choose the User-Agent you want. TwitHunter lacks a lot a features but can send pictures and choose the tweet location. And Twitter cannot prevent TwitHunter from displaying the tweets the way it wants without blocking existing applications.

TwitHunter sources are available on GitHub: https://github.com/nst/STTwitter.

A development version is available at http://seriot.ch/resources/abusing_twitter_api/TwitHunter.app.zip.

6. Security Considerations

Consumer keys are supposed to be kept secret in the OAuth protocol. This can be achieved when the OAuth consumer and OAuth provider are remote web servers (eg. bit.ly and twitter.com). However, we have seen that keeping these tokens secret in a desktop application is much more difficult, if possible at all. Taking OAuth usage from the web to the desktop was somehow a conceptual error from Twitter, since it really can't prevent hacker from extracting consumer tokens. Let us go though what can go wrong when consumers token leak. Note that such a leak can go unnoticed from the application developer until it's too late.

6.1 Reaching application limits

How do leaked consumer tokens affect the API rate limits?

When using a user's context (PIN-based or xAuth authentication), there is a limit in reading for the tuple (client, user) and another limit in writing for the user. Leaking consumer tokens should not exhaust these limits faster since they depend on each user. However, the 100,000 users tokens cap per application may be reached very quickly, resulting in a denial of service for the users of the application.

Now, without user context (application only authentication), anyone knowing the application consumer tokens can get the bearer token. He can then send as many requests he wants to until the application rate limits are exhausted, resulting in a denial of service for the legitimate application users.

Note that rate limits are sent along with response headers, such as in:

{
    "x-rate-limit-limit" = 180;
    "x-rate-limit-remaining" = 179;
    "x-rate-limit-reset" = 1381661076;
}

6.2 Bearer token invalidation

While using the (application only authentication), anybody having an application consumer tokens can get the bearer token, and post a request to invalidate it, resulting once again in an actual denial of service for the legitimate application users who are still using the former, now invalidated bearer token.

6.3 Keys revocation

Twitter says it will systematically invalidate keys of compromised applications (Developer Rules of the Road, II. 3 C), which locks out users using the application, forcing them to use another one or use the Twitter website in order to access the service.

Twitter frequently revokes consumer keys, but more rarely keys from popular applications. However, it did not hesitate to revoke UberTwitter and twitdroyd tokens in February, 2011.

We ask all developers in Twitter ecosystem to abide by a simple set of rules that are in the interests of our users, as well as the health and vitality of the platform as a whole.

We often take actions to enforce these rules; in fact, on an average day we turn off more than one hundred services that violate our API rules of the road. This keeps the ecosystem fair for everyone.

Today we suspended several applications, including UberTwitter, twidroyd and UberCurrent, which have violated Twitter policies and trademarks in a variety of ways.

http://techcrunch.com/2011/02/18/twitter-suspends-ubermedia-clients-ubertwitter-and-twidroyd-for-violating-policies/

To restore API access after a key is invalidated, the developer of a compromised application has to register a new key, prepare a new version of its application with the new key inside and have its users upgrade after deploying the application, which may reqiore a lengthy review process such as in Apple App Store.

6.4 OAuth Session fixation attack

Also, leaked keys can expose the user to be stolen his user access tokens with an OAuth session fixation attack empowered by a social engineering attack. It goes like that:

  1. the attacker asks for request tokens and signs the request with the keys of a popular application
  2. the attacker puts his own server pirate.net in the oauth_callback parameter in the request token phase
  3. the attacker tricks the user into clicking the link and authorizing the compromised app
  4. twitter sends the verifier to pirate.net?oauth_token=xxx&oauth_verifier=yyy
  5. optionally, the pirate server can redirect the user to twitter.com so that the victim won't even realise he's been powned

The user has chosen to authorize some application to access his account, but the verifier was received by the attacker. This verifier is all the attacker needs to ask and receive valid access tokens for the user.

Also, it is to be noted that there's no formal verification of client identity, so you can register "Twitter for Windows 8" and trick users into trusting it by posting the token request URL in a popular forum.

This vulnerability was supposed to be fixed with OAuth 1.1a. However, Twitter does not fully enforce it and that's why the attack is still possible with some consumer tokens from improperly configured applications, such as TweetDeck, Tweetbot or Twitterrific for Mac, for instance.

6.5 Sending passwords over the network

Twitter insists on using OAuth to identify the client application, although it raises many issues.

One of the main reason asserted by Twitter to promote OAuth is that OAuth doesn't send the password over the network.

First, we can object that HTTP Digest authentication does achieve the same goal.

Second, the most common Twitter clients such as official Twitter clients, OS X and iOS Twitter integration but also many third-party client use xAuth to retrieve the user access tokens, and xAuth sends the username and password in clear over the network.

Nevertheless, OAuth still has the advantage of not requiring the client application to store the password, but just storing the access tokens instead.

6.6 Fighting spam

Twitter claims that the consumer keys are needed to kill applications used by spammers, but OAuth was simply not designed to be used for that purpose. Additionally, it may not be efficient at all, since spammers will use consumer tokens from official clients, and blocking official clients is not an option. Closing individual spammer accounts makes much more sense.

6.7 Keys protection

The consumer tokens are fundamentally insecure when used within a client application. Additionaly, requesting the consumer keys to be kept secret effectively kills open-source applications.

Twitter asks developers to protect their keys in an environnment where users have complete control over the execution flow and access to full address space, so it's impossible to prevent keys extraction.

This problem is somehow similar to the DVD / HDMI / HDCP decryption. At some point, the user has to use a machine that will load in memory cryptographic keys that will be use to decrypt the protected content. It's just a matter of time and motivation until motivated hackers extract the keys and can replicate the decryption process.

Twitter's uses OAuth for something it is not made for. Indeed, the OAuth specification cannot be more clear:

"In many cases, the client application will be under the control of potentially untrusted parties. For example, if the client is a desktop application with freely available source code or an executable binary, an attacker may be able to download a copy for analysis. In such cases, attackers will be able to recover the client credentials." http://tools.ietf.org/html/rfc5849#section-4.6

Nevertheless, Twitter keeps asking developers to hide theses keys as they can:

With desktop applications, it's a matter of "best effort" security with your consumer secret and access token secrets. We recommend making it difficult to obtain the keys from a packaged application, while acknowledging that a determined hacker would be able to obtain them.

That's where monitoring and damage control comes in -- we give all app developers the ability to reset/regenerate their consumer key and secret at any time, which is an effective "kill switch" for the former secrets.

We do our best to monitor for abuse and proactively stub out issues when they arise.

Taylor Singletary, Developer Advocate at Twitter https://groups.google.com/forum/?fromgroups=#!msg/twitter-development-talk/Ncy_k42Nwlo/bayfsxxHXZYJ

6.8 Incorrect, downgraded OAuth implementation

Twitter's implementation of OAuth lacks a couple of OAuth specification requirements on the server side. Specifically:

So, strictly speaking, Twitter implements a custom, less secure variant of OAuth.

7. Conclusion

In this article:

It appears from our work that the main reason for switching from basic authentication to OAuth is not user security or spam fighting, but simply third-party applications control.

We also showed that Twitter has currently no technical way of enforcing its new display requirements or any such policy. Restricting access to their API to their official clients only while banning third-party clients is a very hard problem to solve, if possible at all. For sure, they could go the Skype way with an proprietary, complicated protocol but it would not be simple and would raise many new issues.

So, Twitter tries to live with the consumer keys management issue by asking developers to do their best to hide the consumer_secret in their applications, monitoring leaks and revoking the keys when problems arise.