0% found this document useful (0 votes)
24 views

Abusing Twitter Api

Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
24 views

Abusing Twitter Api

Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 16

seriot.

ch - Abusing Twitter API

Beauty will save the world.


seriot.ch Fyodor Dostoevsky

Home
Home Made Maps ABUSING TWITTER API
Hello Mach-O This article is an up-to-date version of a research I first presented at App Sec Forum Western Switzerland
A Tiny NTP Client 2012 and then at Hack in the Box 2013 (Amsterdam).

Abusing Twitter API 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.
Parsing JSON
Data Visualization [2013-04-11] PCWorld

Trail [2013-04-13] Le Monde Informatique


Talks and Papers [2013-10-16] I presented a followup as a rump session at AppSec Forum 2013 (slides).
Contact [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.
Popular resources
Time and Computers [2014-05-22] I presented the STTwitter library at CocoaHeads Lausanne, May 2014 (slides, custom Twitter
Unicode Talk conversation on a dedicated account).
Unicode Hacks
Unicode Poster
Session Description
Abusing Twitter API
Twitter has decided that his web APIs would require signed requests by March 2013 [1]. Hence Twitter will
Twitter API Visual Doc. be able to block any third-party application which fails to meet the new very strict terms of service [2][3].
Hello Mach-O
Parsing JSON is a Minefield 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
PyCairo Visual Doc
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.
External profiles
Twitter [1] https://dev.twitter.com/blog/changes-coming-to-twitter-api
LinkedIn [2] https://dev.twitter.com/terms/api-terms
Facebook [3] https://dev.twitter.com/terms/display-guidelines
GitHub
Table of Contents
Strava
ITRA 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

1 of 16 5/1/20, 10:49 PM
seriot.ch - Abusing Twitter API

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:

third-party clients must display the ads and strictly apply the Twitter layout guidelines
all API requests must be authenticated by both the user and the application he uses

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

2 of 16 5/1/20, 10:49 PM
seriot.ch - Abusing Twitter API

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:

maintain a session
authenticate the user
authenticate the client application
ensure the request integrity

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 of 16 5/1/20, 10:49 PM
seriot.ch - Abusing Twitter API

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 [email protected] 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

4 of 16 5/1/20, 10:49 PM
seriot.ch - Abusing Twitter API

<- {"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.

5 of 16 5/1/20, 10:49 PM
seriot.ch - Abusing Twitter API

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)

6 of 16 5/1/20, 10:49 PM
seriot.ch - Abusing Twitter API

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

(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.pla

(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 'x00x00x00x00' | 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

7 of 16 5/1/20, 10:49 PM
seriot.ch - Abusing Twitter API

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"]

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.

[2014-07-02] Check out this visual documentation of Twitter API I just created: twitter_api.pdf

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

8 of 16 5/1/20, 10:49 PM
seriot.ch - Abusing Twitter API

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

9 of 16 5/1/20, 10:49 PM
seriot.ch - Abusing Twitter API

+ 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

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 [email protected]:/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

10 of 16 5/1/20, 10:49 PM
seriot.ch - Abusing Twitter API

Authorization header (notice the consumer key):

Authorization: OAuth oauth_nonce="C4E16213-9058-49E8-A06E-65A5D961EED0", oauth_signature_

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/TwitterAuthenticat

11 of 16 5/1/20, 10:49 PM
seriot.ch - Abusing Twitter API

# 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/Twi

# 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.".

12 of 16 5/1/20, 10:49 PM
seriot.ch - Abusing Twitter API

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.

13 of 16 5/1/20, 10:49 PM
seriot.ch - Abusing Twitter API

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.

14 of 16 5/1/20, 10:49 PM
seriot.ch - Abusing Twitter API

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:

15 of 16 5/1/20, 10:49 PM
seriot.ch - Abusing Twitter API

"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:

Twitter does not check that combination of nonce/timestamp/token are not reused. The same request
can be replayed for a few minutes, and new requests can reuse a former, already used (nonce,
timestamp, token) combination.
A constant nonce can be reused across requests. It can even be "0".

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

7. Conclusion

In this article:

we've talked about Twitter evolution from an open, geek-friendly network towards a closed, big-media
and advertisers friendly network, and the consequences on Twitter API;
we've presented the OAuth protocol with focus on the process to receive access tokens;
we've used some reverse engineering techniques to extract OAuth consumer tokens from popular
Twitter clients;
we've exposed hardcoded Twitter tokens, their locations and the way they are obfuscated;
we've built a modern Objective-C library to use Twitter API v1.1 and a prototypic Twitter client on top
of it;
we've seen how to impersonate popular Twitter clients;
we've explained how to craft accounts creation requests similar to iOS ones;
we've demonstrated a practical session fixation attack;
we've discussed this situation from a security standpoint.

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.

Copyright - seriot.ch

16 of 16 5/1/20, 10:49 PM

You might also like