이번 LINE CTF의
웹 분야 2번 문제 입니다.
# 개요
정규표현식으로 작성된
입력값 필터링을
우회하여
FLAG를 획득하는 문제
입니다.
# 환경 구성
문제 파일을 다운받으면,
Docker Image의 내용이 명시된
Dockerfile과
docker-compose.yaml 파일이
있습니다.
아래 명령어로
도커 컨테이너를 설치 및 실행시킬 수 있습니다.
# DockerFile과 docker-compose.yaml 파일이 있는
# 디렉토리로 이동
> cd dist
# Docker 컨테이너 설치&실행
> docker-compose up
설치된 후 실행 중인 컨테이너의 CMD 창은
종료해도 상관 없습니다.
아래 명령어를 통해
실행 중인 컨테이너의 PORT를
확인할 수 있습니다.
# 실행 중인 Docker Container 확인
> docker ps
도커가 실행 중인 서버(호스트)의
IP와 PORT로 접속하면
문제 사이트를 확인할 수 있습니다.
# 분석
문제로 제공된 파일들 중
중요한 파일은
main.pl
파일입니다.
+ main.pl
main.pl 파일의 내용입니다.
사용자로부터
password 파라미터를 입력받은 후
필터링을 거쳐
마지막에 eval 함수의 결과가 TRUE이면
FLAG를 획득할 수 있습니다.
필터링 규칙은
정규표현식으로 이루어졌으며,
아래와 같습니다.
필터링 규칙:
- 패스워드가 비어있지 않은지 확인
- 패스워드가 20자 이하인지 확인
- 패스워드가 알파벳, 숫자, 하이픈(-) 또는 언더스코어(_)만 포함하는지 확인
- 패스워드가 알파벳, 숫자, 하이픈 또는 언더스코어 중 적어도 하나씩을 포함하는지 확인
- 패스워드가 "0-9_-boxe" 중 하나라도 포함하지 않는지 확인
- 패스워드가 eval 인젝션을 허용하는 문자열을 포함하지 않는지 확인
- 패스워드가 "Mx. squ1ffy" 중 하나라도 포함하지 않는지 확인
-
#!/usr/bin/perl
-
-
# ...
-
-
my $pw = uri_unescape(scalar $q->param("password"));
-
-
# password 파라미터 확인
-
if ($pw eq '') {
-
print "Hello :)";
-
exit();
-
}
-
-
# 길이 20 제한
-
if (length($pw) >= 20) {
-
print "Too long :(";
-
die();
-
}
-
-
# 입력값은 숫자, 영대소문자, '_', '-'
-
if ($pw =~ /[^0-9a-zA-Z_-]/) {
-
print "Illegal character :(";
-
die();
-
}
-
-
# 숫자, 영대소문자, 특수문자(_, -) 3가지 종류 모두 포함
-
if ($pw !~ /[0-9]/ || $pw !~ /[a-zA-Z]/ || $pw !~ /[_-]/) {
-
print "Weak password :(";
-
die();
-
}
-
-
# 숫자, 특수문자(_, -) 바로 뒤에 b, o, x, e 불가
-
# Hex, Octal 등 다른 진수를 입력할 수 있기 때문
-
if ($pw =~ /[0-9_-][boxe]/i) {
-
print "Do not punch me :(";
-
die();
-
}
-
-
# 기본 선언된 문자열 사용 불가
-
if ($pw =~ /AUTOLOAD|BEGIN|CHECK|DESTROY|END|INIT|UNITCHECK|abs|accept|alarm|atan2|bind|binmode|bless|break|caller|chdir|chmod|chomp|chop|chown|chr|chroot|close|closedir|connect|cos|crypt|dbmclose|dbmopen|defined|delete|die|dump|each|endgrent|endhostent|endnetent|endprotoent|endpwent|endservent|eof|eval|exec|exists|exit|fcntl|fileno|flock|fork|format|formline|getc|getgrent|getgrgid|getgrnam|gethostbyaddr|gethostbyname|gethostent|getlogin|getnetbyaddr|getnetbyname|getnetent|getpeername|getpgrp|getppid|getpriority|getprotobyname|getprotobynumber|getprotoent|getpwent|getpwnam|getpwuid|getservbyname|getservbyport|getservent|getsockname|getsockopt|glob|gmtime|goto|grep|hex|index|int|ioctl|join|keys|kill|last|lc|lcfirst|length|link|listen|local|localtime|log|lstat|map|mkdir|msgctl|msgget|msgrcv|msgsnd|my|next|not|oct|open|opendir|ord|our|pack|pipe|pop|pos|print|printf|prototype|push|quotemeta|rand|read|readdir|readline|readlink|readpipe|recv|redo|ref|rename|require|reset|return|reverse|rewinddir|rindex|rmdir|say|scalar|seek|seekdir|select|semctl|semget|semop|send|setgrent|sethostent|setnetent|setpgrp|setpriority|setprotoent|setpwent|setservent|setsockopt|shift|shmctl|shmget|shmread|shmwrite|shutdown|sin|sleep|socket|socketpair|sort|splice|split|sprintf|sqrt|srand|stat|state|study|substr|symlink|syscall|sysopen|sysread|sysseek|system|syswrite|tell|telldir|tie|tied|time|times|truncate|uc|ucfirst|umask|undef|unlink|unpack|unshift|untie|use|utime|values|vec|wait|waitpid|wantarray|warn|write/) {
-
print "I know eval injection :(";
-
die();
-
}
-
-
# Mx. squ1ffy 문자열의 각 문자 포함 불가
-
if ($pw =~ /[Mx. squ1ffy]/i) {
-
print "You may have had one too many Old Pal :(";
-
die();
-
}
-
-
# eval("$pw == 20230325") 이 TRUE가 되야함
-
# $pw가 20230325가 아니라 입력된 값의 실행 결과(eval 함수)가 TRUE
-
if (eval("$pw == 20230325")) {
-
print "Congrats! Flag is LINECTF{redacted}"
-
} else {
-
print "wrong password :(";
-
die();
-
};
# 풀이
필터링을 우회하고
eval 인젝션을 발생시켜
입력값이 20230325가 되도록
해야하는 문제입니다.
숫자만 입력할 수 있으면
쉽게 만들겠지만
영문자와 '-', '_' 또한 포함되야 하므로
많은 고민과 시도를 하던 중
특수 변수를 알게 되었습니다.
(참조: https://www.w3big.com/ko/perl/perl-special-variables.html#gsc.tab=0)
특수 변수 예시(아래 예시보다 많음):
- __END__: 스크립트의 논리적 끝
- __FILE__: 현재 파일 이름
- __LINE__: 현재 행 번호
- __PACKAGE__: 현재 패키지 이름
현재 필요한 건
20230325를 만들기 위한 숫자이므로
__LINE__ 을 사용했습니다.
/cgi-bin/main.pl?password=20230326-__LINE__