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.
Finding Single Page Apps and endpoints backed by APIs inside a Main Web App
Main Web Apps on large targets will typically contain many different apps with different functionality tailored for different types of users. Each of these apps can have their own API, completely separate from other apps on the site. These APIs can vary wildly in how well secured they are, so finding as much content as possible increases the chances of finding at least one vulnerable API endpoint.
I use a combination of the following tools and techniques to discover content within apps and APIs
Google Dorking.
My favorite way to approach a target is find interesting pages index by Google. I could write a whole post on how I use google dorking for bug bounty hunting, but here are a few good techniques I use to get started or look for new content after months of hunting.
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
This is pretty straightforward but it’s still important to note. While authenticated and unauthenticated, click on every link, submit every form, change your email and password, fill out your profile, buy something, register for a premium account, and make sure all traffic is being captured in Burp. I do this in combination with every other content enumeration technique.
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.
JavaScript File and Endpoint Analysis
One of my favorite Burp extensions is BurpJSLinkFinder. I keep it on and check it constantly as I navigate through a target. Javascript files can contain links to other apps, API endpoints, and tons of interesting information about how the site is used.
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
This is my secret sauce but since I doubt most of you are my on my favorite private programs, I’m not too worried about sharing :)
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:
- Change the filter settings in Proxy History to remove all binary and image files
- 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
- Run the script like so:
python burplist.py burprequests.xml
- Use the clean_wordlist.sh script made by @BonJarber to remove junk words and nonsense
- 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
Once I’ve found some interesting targets, found some APIs, or I’m just trying to find more content, I’ll start using some command line 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.
Putting it all together + Exploitation
Using a combination of the techniques above, you’re surely going to have a few worthy API targets backing deeply nestled apps within the main web app.
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
- With a large burp project, I was able to export a custom tailored wordlist containing over 100k unique words using my burplist.py script
- I fired up
ffuf
to start brute forcing with this new wordlist and was quickly blocked by Akamai - 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.
- An interesting word triggered a 302 response and prompted for authentication. It was along the lines of https://example.com/MyExampleApp
- I navigated to the URL, logged in, and was redirected to something like https://example.com/MyExampleApp/dashboard
- 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
- While reviewing Burp’s passively captured Site Map for the main web app of my target I came across an endpoint like so: https://www.example.com/xyzadmin/home
- “xyz” was an acronym for another product I had seen before, but I hadn’t seen this admin endpoint.
- 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
- 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/AddOrUpdateAdmin
… hell 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 /AddOrUpdateAdmin
to 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