Dreamhack - Curling

2025. 3. 20. 15:55·DreamHack/CTF

 

 

 

1. 소스코드

from flask import Flask, request  # Flask 웹 프레임워크와 요청 처리를 위한 모듈
from os import urandom  # 안전한 랜덤 바이트 생성을 위한 모듈
from subprocess import run, TimeoutExpired  # 외부 명령어 실행 및 타임아웃 처리를 위한 모듈

# Flask 애플리케이션 객체 생성
app = Flask(__name__)
# 세션 및 CSRF 보호를 위한 비밀 키 생성 (32바이트 랜덤 값)
app.secret_key = urandom(32)

# 플래그 값 로드 시도: 서버 환경에서 "/flag" 파일 읽기
try:
    FLAG = open("/flag", "r").read().strip()  # 파일에서 플래그를 읽어 공백 제거
except:
    FLAG = "[**FLAG**]"  # 파일 읽기 실패 시 기본값 설정 (테스트용)

# curl 요청이 허용되는 호스트 목록 정의
ALLOWED_HOSTS = ['dreamhack.io', 'tools.dreamhack.io']

# POST 요청으로 들어오는 "/api/v1/test/curl" 엔드포인트 정의
@app.route("/api/v1/test/curl", methods=["POST"])
def admin():
    # 요청 폼 데이터에서 URL 추출 후 공백 제거
    url = request.form["url"].strip()
    
    # 허용된 호스트인지 확인
    for host in ALLOWED_HOSTS:
        if url.startswith('http://' + host):  # URL이 허용된 호스트로 시작하는지 체크
            break #http://dreamhack.io 로 시작해야함
    else:  # for-else: 허용된 호스트가 없으면 실행
        return {'result': False, 'msg': 'Not Allowed host'}  # 비허용 호스트 응답
    
    # 특정 엔드포인트("/test/internal")로의 요청 차단
    if url.endswith('/test/internal'):
        return {'result': False, 'msg': 'Not Allowed endpoint'}  # 금지된 엔드포인트 응답
    
    # curl 명령어 실행 시도
    try:
        # subprocess.run을 사용해 curl로 URL 호출, 결과 캡처, 1초 타임아웃 설정
        response = run(
            ["curl", f"{url}"],
            capture_output=True,  # stdout과 stderr 캡처
            text=True,  # 문자열로 출력 반환
            timeout=1  # 1초 내 응답 없으면 타임아웃
        )
        return {'result': True, 'msg': response.stdout}  # 성공 시 curl 출력 반환
    
    # 타임아웃 발생 시 예외 처리
    except TimeoutExpired:
        return {'result': False, 'msg': 'Timeout'}  # 타임아웃 응답

# GET 요청으로 들어오는 "/api/v1/test/internal" 엔드포인트 정의
@app.route('/api/v1/test/internal', methods=["GET"])
def test():
    # 요청자의 IP 주소 추출
    ip = request.remote_addr
    # 로컬호스트(127.0.0.1)에서만 접근 허용
    if not ip == '127.0.0.1':
        return {'result': False, 'msg': 'Only local access is allowed'}  # 비로컬 접근 차단
    return {'result': True, 'msg': FLAG}  # 로컬 접근 시 플래그 반환

# 메인 실행 블록: 서버 실행
if __name__ == '__main__':
    # 모든 인터페이스(0.0.0.0)에서 8000번 포트로 서버 실행
    app.run(host='0.0.0.0', port='8000')

 

따로 주석을 추가했다.

 

 

 

 

 

2. 분석

# curl 요청이 허용되는 호스트 목록 정의
ALLOWED_HOSTS = ['dreamhack.io', 'tools.dreamhack.io']

# POST 요청으로 들어오는 "/api/v1/test/curl" 엔드포인트 정의
@app.route("/api/v1/test/curl", methods=["POST"])
def admin():
    # 요청 폼 데이터에서 URL 추출 후 공백 제거
    url = request.form["url"].strip()
    
    # 허용된 호스트인지 확인
    for host in ALLOWED_HOSTS:
        if url.startswith('http://' + host):  # URL이 허용된 호스트로 시작하는지 체크
            break #http://dreamhack.io 로 시작해야함
    else:  # for-else: 허용된 호스트가 없으면 실행
        return {'result': False, 'msg': 'Not Allowed host'}  # 비허용 호스트 응답

 

1.POST로 경로 "/api/v1/test/curl"에 요청을 보내야 한다.

2. 허용된 호스트인지 확인하는 과정이 존재한다 ALLOWED_HOSTS 안에 있는 목록들을 참고하면 될 거 같다.

3.URL 은 'http://dreamhack.io'로 시작해야 한다.

 

 

 # 특정 엔드포인트("/test/internal")로의 요청 차단
    if url.endswith('/test/internal'):
        return {'result': False, 'msg': 'Not Allowed endpoint'}  # 금지된 엔드포인트 응답
# curl 명령어 실행 시도
    try:
        # subprocess.run을 사용해 curl로 URL 호출, 결과 캡처, 1초 타임아웃 설정
        response = run(
            ["curl", f"{url}"],
            capture_output=True,  # stdout과 stderr 캡처
            text=True,  # 문자열로 출력 반환
            timeout=1  # 1초 내 응답 없으면 타임아웃
        )
        return {'result': True, 'msg': response.stdout}  # 성공 시 curl 출력 반환
    
    # 타임아웃 발생 시 예외 처리
    except TimeoutExpired:
        return {'result': False, 'msg': 'Timeout'}  # 타임아웃 응답
@app.route('/api/v1/test/internal', methods=["GET"])
def test():
    # 요청자의 IP 주소 추출
    ip = request.remote_addr
    # 로컬호스트(127.0.0.1)에서만 접근 허용
    if not ip == '127.0.0.1':
        return {'result': False, 'msg': 'Only local access is allowed'}  # 비로컬 접근 차단
    return {'result': True, 'msg': FLAG}  # 로컬 접근 시 플래그 반환

 

1.endswith로 /test/internal 에 대한 경로를 금지시킨다. (우회 필요)

2.curl로 URL을 호출시킨다.

3.GET 요청으로 /api/v1/test/internal 에 대한 경로 그리고 로컬로 접속해야지 플래그가 출력된다.

 

 

3. 문제풀이

 

1.POST 는 코드에 나와있는 대로 경로를 입력

2.Content-Type 은 curl에서 url와 같은 키-값 쌍을 서버로 보내기 위해서 사용해야 한다.

3.Not allowed endpoint 가 출력되는 걸 확인할 수 있다.

 

이제 우회를 해보도록 하자.

 

 

 # 특정 엔드포인트("/test/internal")로의 요청 차단
    if url.endswith('/test/internal'):
        return {'result': False, 'msg': 'Not Allowed endpoint'}  # 금지된 엔드포인트 응답

 

이 부분을 우회해야 한다.

 

보면 endswith 함수에 대해서 알아야 한다.

 

text = "hello world"
print(text.endswith("world"))      # True ("world"로 끝남)
print(text.endswith("hello"))      # False ("hello"로 끝나지 않음)

 

이런 식으로 endswith는 문자열 끝에 초점을 맞춰 True, False를 반환시킨다.

 

그렇다면 경로를 건드리지 않는 선에서 그리고 /test/internal 이 연속되지 않게 우회를 해보도록 하자.

 

 

 

 

중간에"./"를 넣어서 우회를 하면 Time out을 출력하는 걸 알 수 있다.

 

 

    # 특정 엔드포인트("/test/internal")로의 요청 차단
    if url.endswith('/test/internal'):
        return {'result': False, 'msg': 'Not Allowed endpoint'}  <우회한 부분>
    
    # curl 명령어 실행 시도
    try:
        # subprocess.run을 사용해 curl로 URL 호출, 결과 캡처, 1초 타임아웃 설정
        response = run(
            ["curl", f"{url}"],
            capture_output=True,  # stdout과 stderr 캡처
            text=True,  # 문자열로 출력 반환
            timeout=1  # 1초 내 응답 없으면 타임아웃
        )
        return {'result': True, 'msg': response.stdout}  # 성공 시 curl 출력 반환
    
    # 타임아웃 발생 시 예외 처리
    except TimeoutExpired:
        return {'result': False, 'msg': 'Timeout'}  <출력된 부분>

# GET 요청으로 들어오는 "/api/v1/test/internal" 엔드포인트 정의
@app.route('/api/v1/test/internal', methods=["GET"])
def test():
    # 요청자의 IP 주소 추출
    ip = request.remote_addr
    # 로컬호스트(127.0.0.1)에서만 접근 허용
    if not ip == '127.0.0.1':
        return {'result': False, 'msg': 'Only local access is allowed'}  # 비로컬 접근 차단
    return {'result': True, 'msg': FLAG}  <출력해야하는 부분>

 

 

현재상황을 보면 우회해서 타임아웃이 발생했고 그다음 해야 할 일은 local로 인식할 수 있게끔 하면 된다.

 

 

3-1.URL 현재, 과거

답을 알려줄 수는 없기에 힌트라도 적어놔야겠다.

 

URL의 현재 표준은 scheme://host:port/path이다.

ex) "http://dreamhack.io:8000/api/v1/test/internal"

 

URL 과거 표준은 scheme://username:password@host:port/path이다.

현재 사용자 이름과 비밀번호는 따로 아는 게 없으니 더미데이터를 넣어 양식을 채운뒤 알맞은 값을 입력해 보자.

 

 

 

'DreamHack > CTF' 카테고리의 다른 글

chockshop  (0) 2024.12.10
Secure Secret  (0) 2024.11.07
(CTF 출제) simple-web-request  (1) 2024.10.09
(CTF 출제) Tomcat Manager  (1) 2024.09.05
(CTF 출제) amocafe  (2) 2024.09.05
'DreamHack/CTF' 카테고리의 다른 글
  • chockshop
  • Secure Secret
  • (CTF 출제) simple-web-request
  • (CTF 출제) Tomcat Manager
G_OM
G_OM
최대한 설명 열심히 하려고 합니다. 궁금한 거 있으면 언제든지 물어보셔도 좋습니다.
  • G_OM
    끄적끄적
    G_OM
  • 전체
    오늘
    어제
    • 분류 전체보기 (138)
      • 모의해킹 (7)
      • Wargame (69)
        • Linux_bandit (33)
        • Webhacking.kr (36)
      • DreamHack (52)
        • WEB (14)
        • Reverising (9)
        • System (0)
        • CTF (22)
      • Android_security (5)
      • 정보보안기사 (2)
      • IT? (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • DreamHack 에 대한 문제들...
  • 인기 글

  • 태그

    bandit
    난독화
    bandit17
    bandit20
    sql injection
    webhacking
    워게임
    overthewire
    cookies
    bandit18
    bandit30
    CTF
    webhacking.kr
    drozer
    Dreamhack
    php wrapper
    php
    url encode
    insecurebankv2
    lfi
    Cookie
    union
    리눅스 워게임
    안드로이드
    정보보안기사
    Linux wargame
    Linux
    Android
    리눅스
    wargame
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
G_OM
Dreamhack - Curling
상단으로

티스토리툴바