Dreamhack | web-HTTP-CLI
web-HTTP-CLI 문제 풀이
본 문제는 dreamhack.io를 통해서 풀어 보실 수 있습니다.
해답을 이해하며 생각을 해보면서 풀이 해보시길 바랍니다.
문제 내용
문제 풀이
def get_host_port(url):
return url.split('://')[1].split('/')[0].lower().split(':')
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('', 8000))
s.listen()
while True:
try:
cs, ca = s.accept()
cs.sendall('[Input Example]\n'.encode())
cs.sendall('> https://dreamhack.io:443/\n'.encode())
except:
continue
while True:
cs.sendall('> '.encode())
url = cs.recv(1024).decode().strip()
print(url)
if len(url) == 0:
break
try:
(host, port) = get_host_port(url)
if 'localhost' == host:
cs.sendall('cant use localhost\n'.encode())
continue
if 'dreamhack.io' != host:
if '.' in host:
cs.sendall('cant use .\n'.encode())
continue
cs.sendall('result: '.encode() + urllib.request.urlopen(url).read())
except:
cs.sendall('error\n'.encode())
cs.close()
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('', 8000))
s.listen()
현재 소켓 통신을 진행하며 8000번 포트를 이용하여 서비스가 이루어지는 것을 알 수 있다.
try:
(host, port) = get_host_port(url)
if 'localhost' == host:
cs.sendall('cant use localhost\n'.encode())
continue
if 'dreamhack.io' != host:
if '.' in host:
cs.sendall('cant use .\n'.encode())
continue
cs.sendall('result: '.encode() + urllib.request.urlopen(url).read())
except:
cs.sendall('error\n'.encode())
localhost에 대한 문자가 url에 포함되는 경우 localhost를 사용할 수 없음을 안내하는 문구를 서버에서 송신하고, dreamhack.io가 아닌 URL 사용 시 . 문자를 사용할 수 없음을 송신해준다.
소켓 통신을 위한 Client 코드는 codezaram을 참고했습니다.
## CLIENT ##
import socket
from _thread import *
HOST = ''
PORT = 9999
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((HOST, PORT))
def recv_data(client_socket):
while True:
data = client_socket.recv(1024)
print("recive : ", repr(data.decode()))
start_new_thread(recv_data, (client_socket,))
print('>> Connect Server')
while True:
message = input()
if message == 'quit':
close_data = message
break
client_socket.send(message.encode())
client_socket.close()
이를 이용하여 통신을 진행해보면 아래와 같다.
. 문자를 사용했기에 통신이 이루어지지 않는다. flag.txt 파일은 서버내에 있으므로 localhost 통신을 통해 파일을 /app/flag.txt 접근하여 파일을 읽어와야 한다.
| Expression | Value |
|---|---|
| Decimal | 2130706433 (127.0.0.1) |
| Omission-1 | 127.1 (127.0.0.1) |
| Omission-2 | 192.168.1 (192.168.0.1) |
| Omission & Octal | 0177.1 (127.0.0.1) |
| Hexademical | 0x8080808 (8.8.8.8) |
| Octal & Hexademical | 010.0x0000008.00000010.8 (8.8.8.8) |
| Decimal & Hexademical | 8.0x000000000000000080808 (8.8.8.8) |
localhost와 .를 우회하기 위해서는 10진수 표현인 2130706433을 이용해야한다.
또한, http:// URI를 이용하는 것이 아닌 file:// URI를 이용하여 로컬에서 파일을 찾는 프로토콜을 이용할 수 있다.
필터링 우회와 프로토콜 변경 시에 별도의 에러가 발생하는데 이 에러에 대해 확인하기 위해 별도의 Flask 서버 구축을 통해 테스트를 진행해봤다.
에러가 발생하는 곳은 request.py의 1532번째줄 open_local_file 함수에서 발생하는 것을 알 수 있다. 어떠한 이유에 의해 발생했는지 확인해보도록 한다.
#--request.py--#
def open_local_file(self, req):
import email.utils
import mimetypes
host = req.host
filename = req.selector
localfile = url2pathname(filename)
try:
stats = os.stat(localfile)
size = stats.st_size
modified = email.utils.formatdate(stats.st_mtime, usegmt=True)
mtype = mimetypes.guess_type(filename)[0]
headers = email.message_from_string(
'Content-type: %s\nContent-length: %d\nLast-modified: %s\n' %
(mtype or 'text/plain', size, modified))
if host:
host, port = _splitport(host)
if host or \
(port and _safe_gethostbyname(host) in self.get_names()):
if host:
origurl = 'file://' + host + filename
else:
origurl = 'file://' + filename
return addinfourl(open(localfile, 'rb'), headers, origurl)
except OSError as exp:
raise URLError(exp)
raise URLError('file not on local host') # <--- 1532 lines
정상 수행을 위해서는 아래의 코드로 넘어가야할 것으로 보인다.
if host:
host, port = _splitport(host)
if host or \
(port and _safe_gethostbyname(host) in self.get_names()):
if host:
origurl = 'file://' + host + filename
else:
origurl = 'file://' + filename
return addinfourl(open(localfile, 'rb'), headers, origurl)
조건문을 확인하면 host나 port가 없을 경우에만 가능한 것으로 보인다.
port를 없애기 위해 file://2130706433/app/flag.txt를 보내면 아래와 같이 port가 아예 구분되지 않았기에 문제가 에러가 발생한다.
Traceback (most recent call last):
File "c:\Users\usr\Desktop\NSHC\File\test.py", line 32, in <module>
(host, port) = get_host_port(url)
^^^^^^^^^^^^
ValueError: not enough values to unpack (expected 2, got 1)
def get_host_port(url):
return url.split('://')[1].split('/')[0].lower().split(':')
문제에서 주어진 코드 중 host와 port를 구분하기 위한 코드이다. 이를 확인해보면 .split(':')와 같이 콜론을 통해 구분짓기에 콜론은 반드시 필요하게 된다.
file://2130706433:/app/flag.txt로 하게 되면 콜론을 기준으로 값이 없는 것이 아닌 NULL로 되어 정상적으로 통신이 되며 플래그를 가져올 수 있게 된다.