Giải này là giải đầu năm 2023 mà team mình chơi, giải này chủ yếu nặng về Reverse về mảng web chỉ có 2 bài nếu bạn nào đam mê Reverve thì có thể xem blog bạn mình có viết wu của giải này ở đây.
Get me
Ở bài này thì GET flag từ API về thì lúc truy cập vào link giao diện là một chuỗi json như này.
Nó hiện ra mình không thể GET nó được mình sẽ dùng curl để post một cái gì đó lên trang web.
Như message thông báo thì cần gửi cho nó một url thì chúng ta cũng sử dụng curl để post lên.
Nó trả về cho chúng ta một link để truy cập và lấy flag.
Knight Search
Analys
Khi truy cập vào trang web chúng ta có thể thấy được giao diện như sau:
Theo đề bài thì web này được code bởi framwork Flask thì những lỗ hổng liên quan tới Flask thường sẽ là SSTI, Path or Directory traversal/Local File Inclusion ....
Mình đã check nhưng trang web lỗi SSTI nhưng không có lỗi gì xảy ra hình như parameter không nhận, mình nghĩ nó là lỗi khác có thể là LFI (Vì trong source code có parameter là filename). Mình sử dụng Burp Suite để check.
Nếu như chúng ta thay đổi parameter xem có lỗi gì xảy ra hay không ?
Xong!!! Lỗi ngoại lệ xuất hiện như này:
werkzeug.exceptions.BadRequestKeyError: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.
KeyError: 'filename'
----Lỗi server không thể hiểu được parameter mà nó thiếu parameter: filename
Một phần trong chế độ Werkzeug debug mode thì có thể xem được đoạn code chứa parameter đó:
@app.route("/home", methods=['POST'])defhome(): filename = urllib.parse.unquote(request.form['filename'])#nó sẽ lấy giá trị của parameter:filename và decode các ký tự như %2F là / data ="Try Harder....." naughty ="../"if naughty notin filename: filename = urllib.parse.unquote(filename)# ở đây sẽ decode lần nữa nếu chưa hết các ký tự đã được encodeif os.path.isfile(current_app.root_path +'/'+ filename):# file name được nối sau url
Phân tích đoạn code trên chúng ta thấy được trang web dính lỗi Local File Inclusio/Path Traversal
Ngoài ra khi truy cập một file name khác ngoài home thì cũng xuất hiện lỗi không tìm thấy template như dưới:
Oke, quay lại lỗi Local File Inclusio/Path Traversal mà trang dính phải, như đoạn code trên chúng ta đã phân tích thì filename được replace các ký tự nói đúng hơn được decode url 2 lần nên bypass filter ../ bằng cách double encode url.
Lúc này chúng ta sẽ gửi nó lên xem có thể khai thác không ?
Nhưng có một điều lạ lùng là khi double encode thì submit trực tiếp trên trang thì nó hiện ra thư mục nhưng qua Brup Suite thì không mà cần thêm một lần encode nữa thì mới xuất hiện.
Vậy là đã khai thác thành công lỗi LFI, bây giờ chúng ta sẽ thử dọc file app.py trong thư mục app mà chúng ta đã biết ở phần trên:
Sau một lúc thử đọc cái file khác nhau để kiếm flag từ thư mục home và app thì nhận ra không tồn lại một file nào flag.txt, lúc này để có thể đọc được toàn bộ file của máy chủ trên thì chúng ta cần RCE. Nhìn đoạn code trên thì tác giả để debug=True thì lúc đó chúng ta có thể RCE qua debug console:
Console sẽ thường bị khóa đi và cần mã pin để mở nó ra, sau một lúc research thì mình đọc được ở trang HackTricks bypass Pin Code.
Chúng ta sẽ xem xét đoạn code generate PIN code ở phía:
defget_pin_and_cookie_name(app): pin = os.environ.get('WERKZEUG_DEBUG_PIN') rv =None num =None# Pin was explicitly disabledif pin =='off':returnNone,None# Pin was provided explicitlyif pin isnotNoneand pin.replace('-', '').isdigit():# If there are separators in the pin, return it directlyif'-'in pin: rv = pinelse: num = pin modname =getattr(app, '__module__',getattr(app.__class__, '__module__'))try:# `getpass.getuser()` imports the `pwd` module,# which does not exist in the Google App Engine sandbox. username = getpass.getuser()exceptImportError: username =None mod = sys.modules.get(modname)# This information only exists to make the cookie unique on the# computer, not as a security feature. probably_public_bits = [ username, modname,getattr(app, '__name__', getattr(app.__class__, '__name__')),getattr(mod, '__file__', None), ]# This information is here to make it harder for an attacker to# guess the cookie name. They are unlikely to be contained anywhere# within the unauthenticated debug page. private_bits = [str(uuid.getnode()),get_machine_id(), ] h = hashlib.md5()for bit inchain(probably_public_bits, private_bits):ifnot bit:continueifisinstance(bit, text_type): bit = bit.encode('utf-8') h.update(bit) h.update(b'cookiesalt') cookie_name ='__wzd'+ h.hexdigest()[:20]# If we need to generate a pin we salt it a bit more so that we don't# end up with the same value and generate out 9 digitsif num isNone: h.update(b'pinsalt') num = ('%09d'%int(h.hexdigest(), 16))[:9]# Format the pincode in groups of digits for easier remembering if# we don't have a result yet.if rv isNone:for group_size in5,4,3:iflen(num)% group_size ==0: rv ='-'.join(num[x:x + group_size].rjust(group_size, '0')for x inrange(0, len(num), group_size))breakelse: rv = numreturn rv, cookie_name
Và giá trị cần để có thể exploit là đoạn code này:
getattr(app, '__name__', getattr (app .__ class__, '__name__')) is Flask
getattr(mod, '__file__', None) is the absolute path of app.py in the flask directory (e.g. /usr/local/lib/python3.5/dist-packages/flask/app.py). If app.py doesn't work, try app.pyc
uuid.getnode() is the MAC address of the current computer,str (uuid.getnode ()) is the decimal expression of the mac address
get_machine_id() read the value in /etc/machine-id or /proc/sys/kernel/random/boot_id and return directly if there is, sometimes it might be required to append a piece of information within /proc/self/cgroup that you find at the end of the first line (after the third slash)
To find server MAC address, need to know which network interface is being used to serve the app (e.g. ens3). If unknown, leak /proc/net/arp for device ID and then leak MAC address at /sys/class/net/<device id>/address.
Convert from hex address to decimal representation by running in python e.g.:
>>>print(0x5600027a23ac)94558041547692
Vậy là những dữ liệu chúng ta cần khai thác có thể khai thác được từ lỗi LFI như sau:
Exploit
Lúc đầu chạy payload nhưng nó cho mã PIN sai nhưng một lúc đọc kỹ thì thấy:
If you are on a new version of Werkzeug, try changing the hashing algorithm to sha1 instead of md5.
Payload cuối cùng như sau: (Thay hàm băm md5()->sha1())