Finding and Exploiting Unintended Functionality in Main Web App APIs

While hunting for bugs on Main Web Apps, I encounter tons of interesting APIs. Some are well secured, obscurely documented, and keep you in your lane despite attempts to poke and prod. Others are surprisingly open and fully exposed via JavaScript files — making them much more interesting targets! Customized content enumeration, JavaScript file analysis, a keen eye, and some luck can net impactful results when it comes to exploiting APIs.

I’ve gathered some thoughts on how I recently found two impactful vulnerabilities in APIs, but the steps and techniques below should be generic enough to help find similar API vulnerabilities — IDORs, Privilege Escalation, Information Disclosure, and more.

BurpJSLinkFinder is OP and needs to be 𝚗̶𝚎̶𝚛̶𝚏̶𝚎̶𝚍̶ buffed

Finding Single Page Apps and endpoints backed by APIs inside a Main Web App

I use a combination of the following tools and techniques to discover content within apps and APIs

Google Dorking.

site:example.com filetype:jsp
Looking for interesting file types can help you fingerprint underlying web technologies. This usually won’t help with APIs since modern app routing hides file types, but it’s good for other bugs and mapping out attack surface.

site:example.com intext:"Example, Inc. All rights reserved."
Copyright strings are good ways to pick up on interesting indexed content. You can find older content by specifying a year within the copyright string.

site:example.com inurl:xyz
This one isn’t dumb, I promise. Change “xyz” to a 2–4 letter acronym for a product or service offered by your target.

site:example.com inurl:management
After taking some time to get to know your target, try dorking for common words and terms related to products and services offered by your target.

site:example.com inurl:register
Try to find apps that allow you to register or sign up for an account. I like words like: register, signup, login, auth, authenticate, logout, signin, profile, account, dashboard, panel, admin, forgot, reset, forgotpw, password, email

site:example.com -ext:pdf -ext:html
Removing static content and common file types in search results helps you focus on more dynamic, interesting aspects of your target.

Manual Enumeration via Browser + Passive Enumeration via Burp

I will typically set scope to just include a keyword so I can find interesting content across all domains that might be relevant to the target. I also like to throw in a few cloud keywords to help gather some interesting passive info. Casting a wide net is important since you’ll have a wealth of information and data to use for further enumeration later.

This is just a @Jhaddix tip, why aren’t you reading his methodology??

JavaScript File and Endpoint Analysis

BurpJSLinkFinder does exactly what it says and parses out links from JS files! However, this information on its own can’t be trusted. Sometimes, it will spit out links like:

/api/v1/account/
/profile?id=

It looks well and good and they may be viable endpoints to investigate. That said, digging deeper into the actual code can reveal more information about how that endpoint is actually used and formatted:

var profileURL = '/api/v1/account/' + type + '/profile?id=' + id

Finding the line of code where a URL is created or where an ajax request is being built are good jumping off points. From there, you can analyze how the code builds these requests and try to look for ways to exploit the API.

While navigating through a site looking for interesting content, I’m banking on BurpJSLinkFinder to find huge JS files full of information detailing API endpoints. A good JS file can provide endless fun, especially if the development team is regularly releasing new versions!

I also recommend this eponymous python script for non-burp workflows.

Custom Wordlist Creation

After about a week manually hacking a main web app, I have a pretty large burp file with thousands of in-scope requests. I’ve gathered hundreds of unique pages and JavaScript files that are teeming with interesting words.

I wanted a way to collect all of these in a big file, so I wrote a quick and dirty python script called burplist.py to extract these words and add them to a wordlist:

https://gist.github.com/bendtheory/1129e85e578f25fa9056520fded66352

This script works, but it’s pretty bad, written in python 2, and needs some love. I’ll hopefully end up re-writing it in Python3 or building a burp extension at some point in the future. (Or maybe you will! Time will tell!)

Here’s what I do to generate a bangin’ custom wordlist:

  1. Change the filter settings in Proxy History to remove all binary and image files
  2. Select a request, hit ctrl-A, right click, and select Save Items in the context menu. I save this as an XML file and keep the “Base64-encode requests and responses” options checked
  3. Run the script like so: python burplist.py burprequests.xml
  4. Use the clean_wordlist.sh script made by @BonJarber to remove junk words and nonsense
  5. Optimization tricks to clean the wordlist further include:
    - Remove all words with numbers
    - Convert to lowercase and remove duplicates (check if you target responds to upper case and lower case paths in the same way)
    - Tweak proxy history and scope to contain more specifically targeted endpoints and domains (i.e. only JavaScript files)

After all this, you’ll have a sweet, fully customized wordlist tailored specifically for your target.

CLI Recon Tools

gau by @hacker_ is the GOAT. I’ll use this in combination with @tomnomnom’s qsreplace and some custom grepping built by @dreyand_. Occasionally, I’ll pass these results (if they’re manageable) to httpx to find valid endpoints. The LinkFinder python script mentioned earlier works well here, too.

For directory brute forcing, I’ll use dirsearch with the default wordlist and ffuf with the custom wordlist I mentioned earlier. I’ll hit both the root directory of a target and any interesting API directories found during enumeration.

@assetnote’s kiterunner is great advancement in API endpoint discovery. I’ve used it a few times already and excited to try it out in more places. This is definitely the future of brute force API discovery.

I’ve also recently been using urlscan.io to gather URL data, similar to gau. I haven’t seen this source being used much in other tools but it’s a nice way to add up to 10k unique URLs for a single domain. User submitted content can sometimes have interesting and unique URLs for your target.

jq would work better here, I know.

Putting it all together + Exploitation

Here are two examples of a real life Single Page Apps backed by APIs with several vulnerabilities that I found while hacking on a private program. I’ve been working on this target for almost a year and I’m still finding new bugs by repeating and experimenting with these techniques.

Example 1 — IDOR Leaking User Profile Data

  1. I fired up ffuf to start brute forcing with this new wordlist and was quickly blocked by Akamai
  2. To continue scanning, I found a production mirror of the site in Spanish that wasn’t protected by Akamai and carried on. Looking for domains using the word “origin” works well here too.
  3. An interesting word triggered a 302 response and prompted for authentication. It was along the lines of https://example.com/MyExampleApp
  4. I navigated to the URL, logged in, and was redirected to something like https://example.com/MyExampleApp/dashboard
  5. BurpJSLinkFinder found a JS file at https://example.com/MyExampleApp/resources/scripts/main.js which contained dozens of links, many of which started with /api
 50 — /api/Account/Login
51 — /api/Account/LogOut
52 — /api/Account/Token
53 — /api/Account/CreateProfile
54 — /api/Account/GetProfile
55 — /api/Account/DeleteProfile
56 — /api/Account/UpdateProfile?
57 — /api/Account/UpdateUserPreferences?

7. When reviewing the code in main.js, each of the URLs above appeared to have been extracted from code snippets like this

this.baseUrl = "/MyExampleApp"login = function(content) {
var url = this.baseUrl + "/api/Account/Login";
var body = {
body: JSON.stringify(content),
};
return this.http.request("post", url, body)
}

8. Reviewing my proxy history showed that a few API requests had already fired to initialize the app.

237   https://www.example.com   POST /MyExampleApp/api/Account/Login
238 https://www.example.com GET /MyExampleApp/api/Account/Token

9. I sent one of these POST requests (with a JSON body) to Intruder and set the payload position to the base API URL. I loaded up all the API endpoints found by BurpJSLinkFinder as payloads and started the attack.

10. I ignored 404 responses and honed in on 405 Method Not Allowed, 400 Bad Request, and 200 OK responses.

11. The GetProfile endpoint was returning a 400 response with something along the lines of this:

{"error":{"code":123,"message":"Invalid userName value"}}

12. Now I know that a JSON parameter called userName was likely needed to retrieve information from the API.

13. Putting my username in userName returned my own profile information, but then I needed to find another username that was using the app to leak information.

14. From here, I started to brute force user names with Intruder by mixing and matching a first initial and a common last name. I used two payload lists, a-z and common-surnames.txt from SecLists

15. After a few hundred requests, Intruder found a response longer than the others and I was successfully leaking PII for valid usernames!

16. From here, I re-ran the same Intruder attack with valid usernames on other API endpoints to find additional leaks and escalate impact

I received a $1000 bounty for that vulnerability.

Example 2 — API Information Disclosure and Privilege Escalation to Admin

  1. “xyz” was an acronym for another product I had seen before, but I hadn’t seen this admin endpoint.
  2. When I navigated to the page, it was empty and had no interesting content. However, BurpJSLinkFinder totally lit up! It found over 450 URLs in a single javascript file called main.js
  3. There were a ton of juicy endpoints to find and test, but a few jumped out at me:
328 — /xyzadmin/api/Users/showUsers
329 — /xyzadmin/api/Users/showAdmins

5. Navigating directly to these endpoints did exactly what I thought they would do… they literally showed me JSON objects for all the users and all the company admins for the entire app!

6. I had an easy high severity vulnerability sitting right there, but I was positive this could become a critical with some more digging.

7. While reviewing the BurpJSLinkfinder dump again, I found another interesting endpoint: /xyzadmin/api/Users/AddOrUpdateAdminhell yeah.

8. Because I’d already leaked JSON objects that appeared to represent “Admin” users from the/showAdmins endpoint above, I had an idea of what I needed to send in a POST request to /AddOrUpdateAdminto escalate privileges:

{
"admins": [
{
"0": {
"email": "",
"firstName": null,
"lastName": null,
"userName": "admin",
"phoneNumber": "",
"password": null,
"userRole": "admin",
"id": "deadbeef-6969-f420-e911-cafebabe",
"active": true,
"type": "Admin"
}
}, {
"1": {
"email": "",
"firstName": null,
"lastName": null,
"userName": "admin2",
"phoneNumber": "",
"userRole": "admin",
"password": null,
"id": "deadbeef-aced-bead-baff-12345678",
"active": true,
"type": "Admin"
}
}
]
}

9. So now I had a general idea of the data that should be sent in the request from what I had already leaked, but I still didn’t know what the final request body would look like.

10. Looking back at the javascript file, I found a function definition that provided some answers on the request structure. The following is highly abridged and non-obfuscated:

function createAdmin(){
var adm = new Admin()
adm.userName = this.newUserNameValue
adm.userRole = "Admin"
var body = new postBody({
user: adm,
modifiedBy: this.loggedInUser
})
this.client.AddOrUpdateAdmin(n)
}

10. From this code, I deduced that the JSON post body to create a new admin would look like this:

{
"user": {
"email": "",
"firstName": null,
"lastName": null,
"userName": "bendtheory",
"phoneNumber": "",
"userRole": "admin",
"password": null,
"id": "deadbeef-aced-bead-baff-87654321",
"active": true,
"type": "Admin"
},
"modifiedBy": "bendtheory"
}

11. In a Repeater tab, I whipped up a POST request to /xyzadmin/api/Users/AddOrUpdateAdmin, sent it, and got back a message along the lines of {"success":true,"message":"OK"}

12. I then reloaded the the /xyzadmin/home page… and I had access to everything. I could add, delete, modify anything from any user across the entire app.

I received a $3000 bounty for that vulnerability.

APIs are hiding all over the place, especially deep within your most cluttered Burp projects. Almost a year into working on the same program where these two bugs were found, I’m still finding new and interesting APIs, rife with IDORs, information disclosures, and privilege escalation vulnerabilities.

Huge shout out to Sean (zseano) who inspired my current bug hunting methodology with his YouTube streams from 2019. A lot of the above is built on the foundational methodology he created (which is now in book form and rocks). Go watch that video / buy the book if you want to get better at reading javascript files and API hacking!

And lastly, if you want to learn more about APIs, deep web recon, and Burp tricks ‘n’ tips, come check out the Bounty Hunters Discord server and hack with us! You can find us at https://discord.gg/bugbounty

Hi! I'm Ben, a Web App Hacker, Bug Bounty Hunter, and Self-Taught Techno Entomologist

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store