Finding Data Leaks In Online Formatters
By: Héctor Martos
Tags: Postman, Data Leak, Ethical Hacking, Cybersecurity Awareness, Hacking

Finding Data Leaks In Online Formatters

If you are a web developer, you may have used any online code formatter. There are hundreds of online code formatters for different purposes, such as formatting HTML, XML, JSON, etc.

A well-formatted code improves the readability of the code — image by Unsplash

A common feature of these formatters is that you can save an online version of your formatted data. It’s critical to notice that this makes your code publicly available to anyone knowing the generated URL.

In this story, I will focus on JSON formatter, an online tool to validate and format JSON content. By making a simple test and with the help of Chrome Developer Tools, I could easily identify the API endpoints that save and get the content of a saved JSON, and the kind of generated URL.

The id of the saved content is an incremental number, while the generated URL is a 6 digit hexadecimal number

There isn’t a direct correlation between the id of the saved content and the generated id used in the URL. This is a way to difficult the extraction of saved data, which would be trivial for incrementally generated URLs.

One way to extract all the saved information would be to explore the full spectrum of ids with 6 hexadecimal characters, from ‘000000’ to ‘ffffff’. This will require 16^6 attempts to check every number in this range, which is equivalent to 16,777,216 requests.

To avoid making this huge amount of requests we can use a randomized algorithm — Las Vegas — by generating a random id in this range. Running this probabilistic algorithm multiple times will reduce the probability of no result.

Extracting saved data

To extract the saved data on JSON formatter we are going to use Postman and Newman, my favorite tools to work with APIs.

The import feature of Postman makes it trivial to replicate the request to get the content of a saved JSON. Just copy the request as cURL in Chrome Developer Tools and import it as Raw text in Postman.

Once we have the request imported, we are going to add a Pre-request Script to generate a random id of 6 hexadecimal digits and store it in an environment variable.

Now we can replace the ‘urlid’ parameter in the body of the request with the generated id.

In the Tests section, we can check if we get a non-empty response and if the content contains any of the keywords. In any case, we are going to send the response to a local ExpressJS API that will store it in a MongoDB collection for later analysis.

Finally, by setting the next request to this same request we create an infinite loop to run this collection with Newman, with a delay of two seconds to avoid overloading the JSON formatter API.

And that’s all, folks!. We just need to leave this collection running for a while to collect some results.

Diving into the collected dataset

During the time the collection was running, I was able to collect 515 results. The most boring part of this process is to search for useful data in the list of retrieved JSONs. Although I added a keyword to reduce this work, it’s still a manual process.

After reviewing the dataset, I found out that the vast majority of the obtained results were dummy or totally useless data.

There were also some curious results like a list of questions for a Trivia game, the answers of a chatbot, promotional codes for a trip to Antarctica, some invoices, or the route of a truck in the United States.

// Example of one of the questions of the Trivia

{

"id": 42592,

"answer": "Aries",

"question": "It's the zodiac sign symbolized by a ram",

"value": 300,

"airdate": "2001-05-11T12:00:00.000Z",

"created_at": "2014-02-11T23:11:27.461Z",

"updated_at": "2014-02-11T23:11:27.461Z",

"category_id": 139,

"game_id": null,

"invalid_count": null,

"category": {

"id": 139,

"title": "5-letter words",

"created_at": "2014-02-11T22:47:42.519Z",

"updated_at": "2014-02-11T22:47:42.519Z",

"clues_count": 110

}

}

A stop for a break of a truck driver in Louisiana — suspiciously close to Jack After Dark night club 🙄

But there were also some really interesting findings in the retrieved data. For instance, I found what seems to be an extract of a veterinary database located in Kuwait, with private data such as names, emails, and phone numbers.



{

"id": 274,

"firstname": "XXXXXX",

"middlename": "XXXXX",

"lastname": "XXXXX",

"email": "

"gender": "0",

"api_token": "XXXXXXXXXXXX",

"dob": "10/08/1975",

"photo": "",

"mobile": "XXXXXXX",

"latitude": "XX.XXXXXXXXX",

"longitude": "XX.XXXXXXXXX",

"block": "XXXXXX",

"street": "XXXXXX",

"judda": "none",

"house": "XXXXXX",

"apartment": "none",

"address": "",

"area_id": "XXXXXX",

"status": "1",

"code": "",

"pet": [

{

"id": 148,

"name": "Moe",

"dob": "11-05-2011",

"breed": "1",

"gender": "0",

"height": "",

"photo": "",

"provider_id": "0",

"temperament": "",

"origin": "Persian",

"size": "1",

"chipno": "XXXXXXXXXXXX",

"weight": "3.5",

"grooming": "12 Week",

"isadopted": "0",

"likes": "0",

"cough": "0000-00-00",

"missyou": "0",

"status": "1",

"neutered": "1",

"spayed": "1",

"adopt_comments": "",

"disabled_comments": "",

"disabled_question": "",

"last_groom_appointment": "0000-00-00",

"next_groom_appointment": "0000-00-00",

"veternary": "",

"groom": "",

"breedname": "Cat"

}

]

} // One of the anonimized clients with her pet"id": 274,": "XXXXXX",": "XXXXX",": "XXXXX",": " XXXXXX@gmail.com ","gender": "0",": "XXXXXXXXXXXX","dob": "10/08/1975","photo": "",": "XXXXXXX",": "XX.XXXXXXXXX",": "XX.XXXXXXXXX",": "XXXXXX",": "XXXXXX","judda": "none",": "XXXXXX","apartment": "none","address": "",": "XXXXXX","status": "1","code": "","pet": ["id": 148,"name": "Moe","dob": "11-05-2011","breed": "1","gender": "0","height": "","photo": "","provider_id": "0","temperament": "","origin": "Persian","size": "1",": "XXXXXXXXXXXX","weight": "3.5","grooming": "12 Week","isadopted": "0","likes": "0","cough": "0000-00-00","missyou": "0","status": "1","neutered": "1","spayed": "1","adopt_comments": "","disabled_comments": "","disabled_question": "","last_groom_appointment": "0000-00-00","next_groom_appointment": "0000-00-00","veternary": "","groom": "","breedname": "Cat"

I also found some private keys, credentials, and API tokens for online services.



{

"id": 2038953,

"contact_details": {

"company_org": "XXX",

"email": "

},

"username": "

"password": "XXXXXXXXXXXX",

"api_2": [

{

"api_key": "XXXXXXXXXXX",

"api_key_name": "N°2"

}

],

"api_keys": {

"v3": [

{

"key": "xkeyXXX-XXXXXXX-XXXXXXX",

"name": "XXXXXX",

"partner": "ObjectID()"

}

]

},

"db_key": "XXXXXXXXX",

"status": "enabled",

"active": "1",

"m_sms_disabled": true,

"t_sms_disabled": true,

"language": "en",

"timezone": "Asia/Kolkata",

"locked": false,

"archived": false,

"forms_disabled": false,

"created_date": "2020-08-11 11:41:16",

"chargebee": {

"localization": "german",

"subscriptions": {},

"payment_auto_collected": false

},

"feature_toggle": {

"new-frontend-graph-stats": "enabled",

"new-graph-stats": "new",

"new-user-history-writings": "new",

"new-user-history-readings": "enabled",

"new-pricing": "enabled"

},

"onboarded": true,

"new_forms": "enabled",

"sso": {},

"paid_plan": false,

"dbs_country": "DE",

"lastseen": "2020-08-11",

"activated_applications": [

"email-campaigns",

"transactional-emails"

]

} // Anonimized credentials"id": 2038953,": {"company_org": "XXX","email": " XXXX@XXXXXX.com },": " XXXX@XXXXXX.com ",": "XXXXXXXXXXXX",": [": "XXXXXXXXXXX",": "N°2"],": {"v3": [": "xkeyXXX-XXXXXXX-XXXXXXX","name": "XXXXXX","partner": "ObjectID()"},": "XXXXXXXXX","status": "enabled","active": "1","m_sms_disabled": true,"t_sms_disabled": true,"language": "en","timezone": "Asia/Kolkata","locked": false,"archived": false,"forms_disabled": false,"created_date": "2020-08-11 11:41:16","chargebee": {"localization": "german","subscriptions": {},"payment_auto_collected": false},"feature_toggle": {"new-frontend-graph-stats": "enabled","new-graph-stats": "new","new-user-history-writings": "new","new-user-history-readings": "enabled","new-pricing": "enabled"},"onboarded": true,"new_forms": "enabled","sso": {},"paid_plan": false,"dbs_country": "DE","lastseen": "2020-08-11","activated_applications": ["email-campaigns","transactional-emails"

But the most interesting finding was the Keystore of a cryptocurrency wallet. For those not familiarized with the cryptocurrency world, a Keystore is one of the two things you need to access to a wallet. The password —the other thing you need — can be cracked with Hashcat using the Keystore if you have enough computational power.

The found wallet contained more than 16,000$ at the time of writing 🤑🤑🤑

Conclusions

If there is one lesson we can learn here is that all exposed information on the Internet might end in hands of somebody else, so we have to be very careful to not publish any private data.

As you have seen, it’s really simple to extract saved data from this online formatting tool, and you can use the same technique with other formatting tools that work similarly.

In case you accidentally publish any private data, you can contact JSON formatter staff to remove the public access to this information. Anyway, as a rule of thumb, you should always invalidate any exposed credentials or tokens.