Attackers want your data and they're getting it from your API
Tim Bond
Longhorn PHP Conference - October 15, 2021
Who am I?
- PHP developer
- API developer
- API connoisseur
- Frontend developer
(when I have to be) - Cyclocross racer
Who am I NOT?
- Lawyer
- Security expert
- Penetration tester
- Knower of everything
- Cat 1 cyclocross racer
What is this talk?
- Things I've seen
- Things I can talk about
- More about mobile apps than web apps
- Somewhat applicable to M2M apps
What is this talk NOT?
- A hacking workshop
- The answer to every question
- The only thing you should consider
- Really about "public" APIs (e.g. Stripe, Twilio)
- Going to have any more cyclocross references
Why attack an API?
- Curiosity/data mining
- Espionage or "competitor analysis"
- Grudge
- No other way to get data
- Want to build their own frontend
- API key application was rejected
- Automation
'Automating' comes from the roots 'auto-' meaning 'self-', and 'mating', meaning 'screwing'.
Image credit: XKCD "Automation", licensed Creative Commons Attribution-NonCommercial 2.5
TL;DR:
- Limit your API responses
- Only the data you need to make the app work
- Only to the user that needs to see it
- Only provide endpoints for what your customers need
- Do you really need an endpoint that lists all users?
- Or could you scope it to specific roles/permissions?
- KISS - Every flag adds another permuatation
Code bugs > everything else
- Google+ breach in November 2018
- 52.5 million users
- Name
- Email address
- Occupation
- Gender
- Age
Write tests!
- Test every endpoint:
- Authenticated
- Unauthenticated
- Every role
- Every query string param
The low-hanging fruit
- SQL injection
- XSS
- DDoS
- Large payloads
Obscurity != Security
Let's hack it!
- Set up a packet sniffer
Packet Capture
Let's hack it!
- Set up a packet sniffer
- Counter measure: use HTTPS
- Set up a Man-In-The-Middle attack
- Counter measure: use certificate pinning
- Bypass certificate pinning
Let's hack it!
- Set up a packet sniffer
- Counter measure: use HTTPS
- Set up a Man-In-The-Middle attack
- Counter measure: use certificate pinning
- Bypass certificate pinning
- Counter measure: none...?
- Maybe: check for root
Let's hack it!
GET http://api.example.com/items
POST http://api.example.com/analytics
PUT http://api.example.com/scan/498044355635
POST http://api.example.com/analytics
Look at the payload: GET /items
[
{
"title": "Bleach",
"upc": 498044355635,
"scanned": false
},
// more items
]
Look at the payload: PUT /scan/nnn
{
"user_id": 1234,
"latitude": 30.3235,
"longitude": -97.7109
}
How can we hack it?
- API has no authentication 🥳
- There are some analytics, but we can probably ignore that
- Upon opening the app, we get a list of all the products
- Just iterate over that list and send 10 PUT requests
- Put it in a cron job and you'll have a coupon every time you shop
- Set it up for your friends (just get their ID)
Let's keep exploring the API
- Try some other routes
- It looks RESTful, so how about
/user/123
- Or
/users
- It looks RESTful, so how about
- Are there docs? Head to
/docs
to find out - Let's try something we know doesn't exist, and see if we get a stack trace
A case for UUIDs
- /users/123
- /users/124
- /users/FA091B58-B0E8-46E9-8DEE-592EBA4D62C4
- ??
Maybe it's time for some auth
- Auth Basic
- Auth Digest
-
RFC 2069 Digest Access Authentication Syntax
Hash1=MD5(username:realm:password) Hash2=MD5(method:digestURI) response=MD5(Hash1:nonce:Hash2)
Maybe it's time for some auth
- API keys?
- Partner/User API keys
- Application API keys
- NEVER use static API keys
- Compromised as soon as they're used
- Generally easy to find in the source code
- Hard to rotate
Want a key? Check GitHub!
Maybe it's time for some auth
- JWT
Maybe it's time for some auth
- JWT
- Know exactly who is making the request
- Can assign to known users and unknown
- Time limited
- Can't revoke one
- But you can revoke them all
- Know exactly who is making the request
What people say...
What it actually looks like
Don't give access to everything
👈 No access to Purchase API
Let 'em sniff, but kill repeatability
- HMAC
- Gather the payload and sign the request
Signed
GET /users/123
API_KEY = NWTPk4
APP_ID = wQrDfM
Unsigned
GET /users/123
API_KEY = NWTPk4
APP_ID = wQrDfM
HMAC = APvNwF
🔒
Getting the signing key
- Look around the app source for a key
- See if the app logs to a console
- Browser console on web
- deviceconsole on iOS
- logcat on Android
Make the signing key dynamic
- Much more difficult to find in code
- Must be deterministic so server can duplicate
Example: concatenate:
- The string length of some class name
- The app version number
- The UTC date + hour
Reverse engineering the runtime
- Connect the app to a debugger
- Decompile the Android app
- Banjo
- dex2jar
- JADX
- JD-GUI
- baksmali
- Works on iOS too--ask the jailbreakers
Proxify the APIs
- App only uses one secret
- Proxy verifies that secret
- Proxy reaches out to the actual APIs on the user's behalf
- Much easier to rotate API auth for 3rd party systems
- But we're still dealing with a secret 😕
Secrets as a Service
API
Shared secret
Unauthenticated request
Secret token
API call ✅
Dynamic
Integrity
Check
Using OAuth 2
API 1
API
Gateway
Authorization request
Auth token
App Auth request
App token
OAuth 2 service
App auth service
API calls
Registered
app info
Registered
user info
API 2
API 3
Key 3
Key 1
Key 2
OAuth 2 flow
- Authorize both who (user) and what (app)
- Only time-limited, runtime tokens
- Don't think of OAuth2 as user auth—it's an access token generator
- Rotate secrets without touching devices
- API gateway does rate limiting and authentication
What about endpoints that can't have authentication?
- Rate limiting
What about endpoints that can't have authentication?
- Rate limiting
- Monitoring
- Heuristics
- IP
- User agent
- Time between requests
- Geo data
- Your API gateway might do this for you!
No API? No problem!
No API? No problem!
<?php
$crawler = $client->get('https://www.example.com/log-in');
$crawler->filter('#email')->sendKeys('tim.bond');
$crawler->filter('#password')->sendKeys('test123');
$crawler->filter('#log-in')->click();
$client->waitForStaleness('#log-in');
$client->waitFor('#price')->filter('#price')->getText();
But we have a CAPTCHA!
- Dozens of services will solve those
- Images recognized with OCR/AI and/or Humans
Conclusion
- If they want it bad enough they will try hard enough
- Don't use static authentication
- Don't build your own authentication
- Debug protection and code obfuscation thwart semi-determined attackers
- Authenticate the app (environment) as well as the user
Questions
Attackers want your data and they're getting it from your API [Longhorn PHP]
By Tim Bond
Attackers want your data and they're getting it from your API [Longhorn PHP]
Longhorn PHP Conference - October 15, 2021
- 88