본 문제는 Dreamhack을 통해서 풀어 보실 수 있습니다.

해답을 이해하며 생각을 해보면서 풀이 해보시길 바랍니다.

문제 내용

해당 문제에서는 어떠한 취약점을 이용해서 문제 풀이하는지 서술되어 있지 않다.

하지만 Raw Socket Sender 서비스를 보았을 때 소켓 통신을 이용한 문제인 것을 짐작할 수 있다.

app.py

#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
import socket

app = Flask(__name__)

try:
    FLAG = open('./flag.txt', 'r').read()
except:
    FLAG = '[**FLAG**]'

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/socket', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('socket.html')
    elif request.method == 'POST':
        host = request.form.get('host')
        port = request.form.get('port', type=int)
        data = request.form.get('data')

        retData = ""
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.settimeout(3)
                s.connect((host, port))
                s.sendall(data.encode())
                while True:
                    tmpData = s.recv(1024)
                    retData += tmpData.decode()
                    if not tmpData: break
            
        except Exception as e:
            return render_template('socket_result.html', data=e)
        
        return render_template('socket_result.html', data=retData)


@app.route('/admin', methods=['POST'])
def admin():
    if request.remote_addr != '127.0.0.1':
        return 'Only localhost'

    if request.headers.get('User-Agent') != 'Admin Browser':
        return 'Only Admin Browser'

    if request.headers.get('DreamhackUser') != 'admin':
        return 'Only Admin'

    if request.cookies.get('admin') != 'true':
        return 'Admin Cookie'

    if request.form.get('userid') != 'admin':
        return 'Admin id'

    return FLAG

app.run(host='0.0.0.0', port=8000)

/socket을 봤을 때 파라미터로 전달 받는 것이 host, port, data 세 개가 존재하는 것을 알 수 있고, host, port를 통해 소켓을 연결하고 data를 모두 보내는 서비스임을 짐작할 수 있다.

/admin에서는 요청하는 HOST가 LOCAL이어야 하고 이외에는 헤더와 쿠키의 값을 조작하며 바디에는 userid=admin이 포함되어야 FLAG를 return해주는 것을 알 수 있다.

문제 풀이

Raw Socket Sender (Done)를 들어가보면 예상한대로 3개의 입력 값을 받는 form이 존재하는 것을 확인할 수 있습니다.

HOSTLOCAL, PORT는 명시되어있는 8000으로 접근할 것이며 DATA는 Raw Socket 통신이라 했으므로 HTTP 헤더, 바디를 구성하여 보내면 될 것으로 예상이 됩니다.

어떠한 메소드를 어느 페이지에 어떠한 HTTP 방식을 사용할 건지에 대한

  • POST /admin HTTP/1.1

어떤 서버에 보내는지 도메인 네임에 해당하는

  • Host: host3.dreamhack.games

사용자가 어떤 클라이언트를 이용하여 요청을 보냈는지

  • User-Agent:Admin Browser

임의의 헤더 값을 위한

  • DreamhackUser:admin

클라이언트의 쿠키를 설정하는

  • Cookie:admin=true

컨텐츠 타입과 문자열 인코딩을 알려주는

  • Content-Type: application/x-www-form-urlencoded

바디에 포함된 메시지의 크기를 바이트 단위로 알려주는(총 12byte이기 때문)

  • Content-Length: 12

서버의 응답 값을 받을 때 image, text, gif 등 어떤 것을 받을지 컨텐츠 압축 방식은 어떤 것으로 할 지 인코딩 방식을 지정해주는

  • Accept: */*

  • Accept-Encoding: gzip, deflate

  • Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7

HTTP 1.0HTTP 1.1 차이에서 해당 연결을 지속적으로 할 것인지에 대한

  • Connection: close

    • 해당 부분은 HTTP 1.1을 주로 사용하면서 keep-alive 옵션이 Default가 되지만 의미가 없어지는 추세

임의의 userid를 바디에 담으며 해당 값을 admin으로 지정

  • userid=admin

해당 내용을 정리해서 Send하면 응답 값으로 플래그가 넘어오는 것을 확인할 수 있다.