Web Application Cheat Sheet
This page has notes I took about basic web security issues from reading The Web Application Hacker’s Handbook in 2015. The descriptions of each class of vulnerability are still relevant, but be warned many of the specific techniques are starting to age.
XSS
What is it
- Ability of an attacker to input HTML/script that runs in the
victim’s browser
- Attacks target the user, not the server
- Reflected
- user visits malicious URL supplied by attacker, malicious input
is sent to server in a request, and is returned in the response
and subsequently processed/executed in victim’s browser
- example: attacker sends victim a link with the payload as a
parameter
- http://www.vulnerable.app/printText.php?text=<script>alert(1)</script>
- Stored
- malicious input is sent to server in a request, and is returned
to and executed by every user that loads that page
- example: comment page on a blog
- attacker comments: <script>alert(1)</script>
- comment appears every time the page loads, and script runs
in each visitor’s browser
- DOM-based
- user visits malicious URL supplied by attacker, URL contains
malicious script/payload
- page contains embedded JavaScript that performs some function
using the page’s URL
- payload within the URL is executed because that function
processed it
- server’s responses never contained payload because it’s all
client-side (executed by the embedded JS)
How to find it
Testing a running application
- Find spots where user input appears on page/within page source
- Payloads might have to be modified to fit the context of the
input location
- example: if input inserted within existing HTML tag or event
handler
- Add “> at the beginning to close existing tags, for
example
- Test with various inputs, see what shows up (test what’s filtered
and what isn’t)
- Script filter evasions
- <script>alert(1)</script>
- <scRipt>alert(1)</scRipt>
- Change case to evade filter searching for ‘script’
- <scr<script>ipt>alert(1)</scr</script>ipt>
- Evade filters that don’t search recursively
- %3Cscript%3Ealert%281%29%3C%2Fscript%3E
- URL-encoded
- some applications will decode the input after the filter
processes it and passes it on
- can also double-encode the payload by URL-encoding the %
signs after the first round of URL encoding
- <object data=”data:text/html;base64,
PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==”>
- Add malicious event handler
- Use decimal or hexadecimal format
- a
- a
- ‘a’ in hexadecimal format
- alert(1)
- can add leading zeros and/or remove semicolon
- Unicode escape
- Try inserting null byte before payload
- %00
- may evade signature-based filters
- Can combine encoding methods
- e.g. URL-encode the \ in \u0061
- Other methods of spawning alert box
- <img src=x onerror=alert(1)>
- <object data=javascript:alert(1)>
- <script>eval(String.fromCharCode(97,108,101,114,116,40,49,41))</script>
- Use javascript packer/minifier
- If your payload executes, or would execute without browser XSS
filter, the site is vulnerable to XSS
Code review
- user input strings are directly inserted into code
- pageTitle = request.getParameter(“title”);
- <? php print “Not found: “ .
urldecode($_SERVER[“REQUEST_URI”]); ?>
How to exploit it
- Send user session cookie to attacker’s website
- e.g. make a request for www.hacker.site/cookie123
- Attacker can see which pages were visited (regardless of whether
they exist or not) and grab session info from there
- alter information that would normally be on the page
- use exploit kit/browser exploit framework
How to fix it
- validate input (server-side)
- limit input length
- only allow a certain subset of expected characters (e.g. only
allow numbers for age value)
- regex matching
- (be sure to test the impact of null bytes on your input filter)
- validate output
- HTML-encode problem characters
- < encoded to <
- > encoded to >
- ‘ encoded to '
- “ encoded to "
- & encoded to &
- Use backslashes to escape problem characters when inserting user
input into JavaScript
- quotation marks
- backslashes
- using a security encoding library makes all of this easier
- eliminate dangerous input points
- within existing script tags
- within existing event handlers
- both are very difficult to secure and have a high risk of filter
evasion
SQLi
What is it
- ability of an attacker to escape an input location in a SQL query
string and finish the string with his/her own SQL that will be
executed on the backend DBMS
How to find it
Testing a running application
- add escape characters (‘ “ ` etc.) or comments (# --) on the end
of your normal input and see how the application handles it
- generic “login failed” messages
- might be blind injection (non-verbose errors) -> keep
trying
- try time delay-based injection and see if it’s successful
- or might not be injectable
- error/anomaly/something’s not right
- probably injectable, try writing a payload
- probe for the structure of the query
- fingerprint the DBMS if necessary
- is it a SELECT, INSERT, UPDATE, or DELETE statement?
- infer based on the functionality of the application
- login function? probably SELECT
- register user function? probably INSERT
- where is user input located within the query?
- usually within WHERE clause of a SELECT statement
- infer based on functionality of the application
- does the user input change order of the results?
probably ORDER BY
- if SELECT, how many columns are being selected?
- use UNION SELECT and add columns one by one until the query
executes successfully
- UNION select 1; #
- UNION select 1, 1; #
- UNION select 1, 1, 1; #
- and so on..
- selecting 1 will work for both string columns and
numerical columns because the database will interpret
the context
- use ORDER BY and increase the column number until you reach
an error
- ORDER BY 1
- ORDER BY 2
- and so on..
- when the number is greater than the amount of columns,
it will trigger an error
- create a test payload and modify it to fit the existing syntax of
the query
- try payload without escape characters
- input might not be quoted
- example: test or 1=1; #
- try to fit payload into existing SQL without attempting to
finish the query yourself
- example if single quote is being used:
- keep in mind the location of the input within the SQL query
- some payloads might not be possible due to where they’re
being inserted
- example: when injecting in an ORDER BY clause, the database
won’t accept UNION, WHERE, AND, or OR at this point
- sqlmap
- good way to check when you can’t find anything by hand
- good at automating when things get tricky
- e.g. exfiltrating 1 char at a time using ASCII char codes
- won’t find everything, however
Code review
- SQL query strings are concatenated with unfiltered user input
- String query = “SELECT * FROM accounts WHERE custID=’” +
request.getParameter(“id”) + “’”;
How to exploit it
- read critical data from database
- list of database users (if db account used within app
has permission)
- list of web app users/passwords (usually a table in
the database)
- extract crucial web app data
- malicious actions on database
- modify, add, or remove data
- shutdown DBMS
- attempt to priv esc/get shell on database server
How to fix it
- parameterized queries/prepared statements
- using a function that safely handles user input to generate
query, passing the input in as a parameter
- parameterized query is a two-step process:
- specify the query structure (leave placeholders for
user input)
- fill in the placeholders with user input
- user input will always be handled as data and will no longer be
able to change the structure of the query
- defense-in-depth
- use DB account with as few permissions as possible
- remove or disable unnecessary DB functions
- such as xp-cmdshell in MSSQL
CSRF
What is it
- attacker can cause the user to submit unwarranted requests and
subsequently perform actions on a web application without their
knowledge
- attacker can submit requests on user’s behalf, but can’t see
responses due to same-origin policy
How to find it
- look for critical application function to target (e.g. reset
password, create new user)
- determine if the function relies solely on cookies to track sessions
- determine if all request parameters are known/predictable
- will not work if there’s a CSRF token or other necessary
parameter whose value can’t be predicted
- if yes to both of the above, it’s most likely CSRF vulnerable
How to exploit it
- launch request as soon as attacker’s page loads
- if application function uses GET requests, attacker creates img
tag with src = the malicious request
- if application function uses POST requests, attacker creates
form, puts parameters in hidden fields, and submits it as soon
as the page loads
- end goal: submit malicious requests that perform critical actions,
such as:
- transfer money (banking app)
- change password
- grant permissions
How to fix it
- anti-CSRF token
- submitted as a hidden parameter in forms
- server expects a certain token, checks token of form submission
to see if it matches
- if it doesn’t match, the server doesn’t process the submission
- token needs to be unique and non-predictable
UI Redress
What is it
- attacker loads target application in an iframe and layers a
misleading UI over it
- victim clicks on attacker’s UI, when in reality they are interacting
with the target app beneath
- can maliciously interact with target app by:
- initiating two-step process by CSRF, then using UI redress
to complete the second step that’s protected by an anti-CSRF
token
- this kind of attack evades anti-CSRF tokens, because the token is
submitted when the user (unknowingly) submits a form
- in the iframe, the application is running “normally”, so the
anti-CSRF token is received and sent without issues
- from the server’s view, it appears as if the user submitted the
form on their own accord, so there is no reason for suspicion
Framebusting
- common defense, but can be easily circumvented
- attempts to check if application is being loaded within an iframe,
and if it is, tries to ‘bust’ out of the frame by:
- reloading itself into the topmost frame on the page
- refusing to load
- loading error page
- etc.
- there are various ways to work around framebusting checks
How to fix it
- framebusting is generally not a reliable defense
- use X-Frame-Options header in HTTP response
- two possible values:
- deny
- browser will prevent page from being framed
- sameorigin
- browser will prevent page from being framed by
third-party domains
Insecure access controls
What is it
- critical app functions do not verify that the issuer of the request
has permission to carry out that action
- e.g. regular user account can access admin function by browsing
to the exact URL of the function
- access control categories:
- vertical
- allows different types of users to access different types of
functionality
- example: admin can view profiles, create profiles,
remove profiles
- regular user can only view profiles
- when vertical access controls are broken:
- users can perform functions that they don’t have
permission to
- example for the above scenario: a regular user
creating profiles
- horizontal
- defines the breadth/scope of resources a user can apply a
function to
- example: user has the permission to view email (a
vertical access control), but can only view their own
email and not anyone else’s (horizontal access control)
- when horizontal access controls are broken:
- users can apply functions to a wider extent of resources
than they’re normally allowed
- example: user can view their own email, and anyone
else’s email in the company
- context-dependent
- restricts user access based on the current application state
- example: prevents user from accessing steps out of order
in a multi-step process
- when context-dependent access controls are broken:
- user can exploit flaw in the application logic to
perform unauthorized or unintended functionality
- example: user skips the payment step when placing an
order
How to find it
- browse the site through a proxy application (Burp, ZAP, etc.) to
create a site map
- use accounts with varying privilege levels to map out the entirety
of the application’s functions
- unregistered, regular user, admin, etc.
- once functions have been enumerated, try repeating the same actions
with a lesser-privileged user
- see if the functionality performs the same way
- the proxy used to create the site map will most likely have
functionality to assist with this
- same idea for multi-step processes, except they require a bit more
attention
- test each request individually to test each step’s access
controls
- goal is to find any steps in the process that assume you must
have gotten there legitimately and don’t enforce or enforce weak
access controls
- see if the application uses the Referrer header as an access
control
- if so, this can be changed by the user (meaning it’s a bad
access control)
- look for and change access control parameters, and try adding your
own if they don’t exist
- try changing resource parameters and see if you can access any
unintended resources
- such as documentID=82736
- try changing to 1, see if IDs are assigned sequentially, etc
How to exploit it
- carry out administrative actions
- change passwords, grant permissions, etc.
- access unintended information
- another user’s documents or email
- modify or remove information
- delete someone’s calendar
- change your salary
- privilege escalation
- within the application (e.g. user -> admin)
- then can attempt to escalate into the OS
How to fix it
- essentially, don’t trust the user
- don’t assume functionality is secure because the user will never
be able to find it
- e.g. long URLs, unique document IDs, etc.
- still need effective access controls on these
- don’t trust any client-side access controls or access controls
that the user has the power to change
- such as admin=true parameters
- don’t assume users will complete multi-step processes in the
correct order
- make access control decisions based on the user’s session
- use a function/interface that can be used throughout the application
to enforce access controls
- this way, everything is uniform and can be updated quickly
throughout the entire application
- defense-in-depth
- switch between different-privileged DBMS accounts depending on
the application action being performed
- low-privileged action uses low-privileged account
- access to individual DBMS tables can be limited to certain DBMS
accounts as well
Command injection
What is it
- escaping a parameter in a command that runs on the OS and writing
your own command that will be executed
How to find it
- find a page where user input is being included in an OS command
- e.g. function that pings an IP and displays output
- attempt to escape the current command and execute your own command,
observe results
- ; ls
- | ls
- || ls
- & ls
- && ls
- ` ls `
- try time delays if the command output is blind/non-verbose
How to exploit it
- cat /etc/password
- read other OS data
- remove files
- PHP shell
- …anything the app’s user account (typically www-data) has the
power do to
How to fix it
- exposing OS commands to user input should be avoided whenever
possible
- use whitelist if possible
- restrict character set of user input depending on expected input
- use command APIs
Directory traversal
What is it
- application interacts with filesystem using unvalidated user input
- e.g. user specifies file name for a photo a photo gallery app
- attacker submits malicious input and can browse to directories of
his choosing and access the files within
How to find it
Testing a running application
- find a dynamic page that interacts with files based on user input
- try adding ../ or ..\ to navigate to the parent directory
- can add many ../ to go all the way up to the root (excess ../
won’t cause problems because the root is the highest you can go,
trying to go farther just returns root again)
- if the app has the functionality to view files, try viewing
an expected system file such as /etc/passwd or
/windows/win.ini
- if this works, the application is vulnerable
- avoiding filters
- try /etc/default/../passwd instead of /etc/passwd
- if the results are different, there might be a filter
modifying the input
- if identical, there probably isn’t a filter
- try different encoding schemes on the path payload
- URL encoding
- Double URL encoding
- Unicode encoding
- put one traversal sequence within another
- ….//
- similar to <scr<script>ipt>
- if filter is checking file endings, put a bogus ending
- /etc/passwd%00.jpg
- null byte may terminate the string depending on the
execution environment, but the filter still sees the
.jpg and validates the input
Code review
- user input passed directly to filesystem API without validating
input or verifying file has been selected
How to exploit it
(ability to read or write depends on the application function)
- reading critical OS files
- /etc/passwd
- other OS info useful for penetrating system
- reading critical business data on filesystem
- intellectual property, patient records, etc.
- overwriting files
- overwrite data files with your own data
- overwrite config files if possible
How to fix it
- like OS commands, user input should not be used to interact with the
filesystem whenever possible
- after processing the file path (decoding and canonicalizing), check
for ../ ..\ or null bytes
- if any are found, stop processing the request
- do not attempt to sanitize, because the request is malicious (is
not legitimate and does not need to be processed)
- check file type of requested file and compare it against a list of
acceptable types