문제 정보는 다음과 같다.
Raw Socket Sender가 구현된 서비스입니다. 요구하는 조건을 맞춰 플래그를 획득하세요. 플래그는 flag.txt, FLAG 변수에 있습니다. |
해당 문제 정보를 파악하고 가상환경에 접속해보자.
접속하여 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)
이번에도 코드 양이 어마어마하다..
하지만, route 설정이 /socket과 /admin 두개니까 얼른 해석해보도록 하자.
일단은 /socket으로 라우팅된 함수인 login을 살펴보자면,
POST 메소드를 보낼때 host, port, data 변수를 받고 있고,
그 다음 소켓 통신을 하고 있다.
코드에서는 socket 라이브러리를 사용하고 있다.
우선 settimeout을 3으로 지정되어 있고,
(socket.settimeout(value)는 초로 표현된 값을 주는 타임아웃을 말한다)
host와 port를 받아서 서버에 접속한다.
그 다음, data 변수를 인코딩하여 메시지를 전송한다.
해당 구문이 잘 수행되면,
recv 서버로부터 응답받은 메시지를 반환하고
빈 값인 retData에 반환된 메시지를 디코딩한다.
예외가 아니면 data에 retData를 리턴하고,
tmpData가 없으면 break가 걸린다.
마지막으로 /admin 라우트를 보자면,
admin 함수에서는 총 5가지의 if 조건문이 설정되어 있다.
1) 로컬 주소인 127.0.0.1이여야하고
2) http 헤더에 User-Agent가 Admin Browser이여야하고
3) http 헤더에 DreamhackUser가 admin이여야하고
4) 쿠키에 admin이 true여야하고
5) form에서 받는 userid가 admin 이여야 한다.
모든 if문을 통과하면 FLAG를 리턴한다.
후.. 코드 해석은 이정도로 하고,
해석한 내용을 바탕으로 문제를 풀어보도록 하자.
일단 호스트에는 127.0.0.1 로컬 주소가 들어가야하고 (조건1번)
Flask 서버가 8000번 포트로 열리기 때문에 port는 8000을 작성해준다.
그리고 아무 데이터나 넣으면 timed out을 발생시킨다.
3초동안 아무 응답이 없으면 타임아웃이 발생되게 코드가 짜여있기 때문이다.
그래서 내가 접근한 방향은 제목이 proxy인 만큼 proxy를 잡아보자는 것이였다.
그럼 다음과 같이 나온다.
해당 프록시에서 넘어가는 http 헤더 내용과
바디 내용을 보여주고 있다.
여기에서 User-Agent를 Admin Browser로 바꿔야하고 (조건2번)
헤더에 없는 DreamhackUser: admin을 추가해주자. (조건3번)
그다음 헤더에 없는 쿠키를 Cookie: admin=true로 설정해주고 (조건4번)
바디에 userid=admin을 작성해준다.
❗근데 여기서 주의할 점은 프록시에서 바로 작성하여 Forward 하는 것이 아니다..
이렇게 생각해서 /socket으로도 넘겨보고 /admin으로도 넘겨봤는데,
결론적으로 form에 userid도 작성해야하고 무엇보다 remote_addr을 설정해야하기 때문에
Data 폼에 작성해주어야 한다.
작성은 다음과 같다.
POST /admin HTTP/1.1
Host: host3.dreamhack.games
Content-Length: 12
Content-Type: application/x-www-form-urlencoded
User-Agent: Admin Browser
DreamhackUser: admin
Cookie: admin=true
userid=admin
우리가 FLAG를 리턴받으려면 /admin으로 날려줘야하고,
Host~Content-Type은 헤더를 정상적으로 보내려면 남겨야한다.
(ps. 예전에 프론트에서 서버에 Post 할 때 생각해보면
Content-Type을 담아서 보낸거랑 비슷한 것 같다.)
그 다음, User-Agent에 Admin Browser를 작성해주고,
헤더에 없는 DreamhackUser와 Cookie를 작성해준다.
마지막으로 바디를 작성해주는데,
바디의 총 길이는 12자로 Content-Length를 12로 수정해준다.
그 다음 Send를 하면 ❗
다음과 같이 플래그가 출력되는 것을 확인할 수 있다.
'CTF > 웹해킹' 카테고리의 다른 글
[Dreamhack] simple-ssti (0) | 2022.09.06 |
---|---|
[Dreamhack] php-1 (0) | 2022.09.01 |
[Dreamhack] pathtraversal (0) | 2022.08.29 |
[Dreamhack] Web Hacking 초급 강의 후기 (0) | 2022.07.14 |
[Dreamhack] xss-2 문제 풀이 (0) | 2022.07.11 |