리눅스 expect는 커맨드 라인 인터페이스 환경에서 스크립트를 자동화하는 프로그램이다.
Telnet, SSH, FTP 등 서비스와 같이 사용자와 상호 작용이 필요한 환경에서 특정 문자열을 화면에 출력하고, 출력한 문자열을에 대한 적절한 응답 데이터를 전송하는 등 이런 모든 과정을 자동화한다.
예를 들면, 리눅스 서버에 스크립트를 업로드하여 업로드 된 디렉토리로 이동한다. 디렉토리 내의 스크립트를 실행하면 사용자와 상호 작용하는 데이터를 반드시 입력해야 스크립트가 실행된다. 정상적인 데이터를 입력하면 스크립트가 실행되어 최종 결과 데이터가 생성되는데, 결과 데이터를 로컬PC로 업로드한다.
이런 과정을 수동으로 해야하는데, 만약 작업을 해야할 서버의 수가 1,000대라면 동일한 작업을 1,000번 수행해야하므로 비효율적이다.
하지만, expect를 이용하면 모든 과정을 자동으로 수행하며, 하나의 서버 뿐만 아니라 모든 서버를 자동화한다.
expect로 구현된 스크립트는 .exp 확장자를 가지므로 기본적으로 로컬 리눅스에 expect 프로그램이 설치되어 있어야 사용이 가능하다.
expect 스크립트 구성
expect 프로그램을 활용한 예제는 여러 대의 서버에 자동화 스크립트를 업로드 및 실행하고, 실행돤 결과물을 로컬PC로 다운로드 하는 과정을 자동화한다.
○ main_shell_script.sh : expect가 설치된 로컬 리눅스에서 실행할 스크립트로 모든 과정을 자동화하는 코드 존재
○ server.txt : 취약점 진단을 수행할 리눅스 서버 아이피, 일반 사용자 아이디, 일반 사용자 패스워드, root 패스워드 존재
○ server_put.exp : 취약점 진단을 수행할 리눅스 서버에 스크립트를 업로드, scp 명령어 자동화
○ server_execute.exp : 취약점 진단을 수행할 리눅스 서버에서 원본 스크립트를 실행
○ server_get.exp : 원본 스크립트를 실행하면 결과물이 생성되는데, 결과물을 로컬 리눅스 서버로 업로드
○ server_delete.exp : 취약점 진단을 수행할 리눅스 서버에 업로드된 파일 삭제
○ server_vuln_check.sh : 리눅스 서버를 취약점 진단을 자동으로 해주는 원본 스크립트
○ res : 결과물이 생성될 디렉토리
server.txt : 서버 정보
로컬 리눅스 서버에 expect 프로그램이 설치된 상태에서 server.txt 파일에 진단을 수행할 서버 정보를 저장한다.
서버 정보는 틸드(~)를 구분자로 하여 아이피 주소~일반 사용자의 아이디~일반 사용자의 패스워드~root 패스워드 형식으로 저장해야 한다.
일반 사용자와 root 계정 정보가 필요한 이유는 scp 명령어로 진단할 서버의 일반 사용자 계정으로 접속 후 root 계정으로 전환하여 스크립트를 진행하기 위함이다.
# server.txt #
10.10.10.31~dazemonkey~1234~1234
10.10.10.32~dazemonkey01~4321~4321
10.10.10.33~dazemonkey02~5678~8765
server_vuln_check.sh : 취약점 진단
server_vuln_check.sh는 취약점 진단을 수행할 리눅스 서버에서 실행할 보안성 검토 스크립트이다. 해당 스크립트는 주요정보통신기반시설 기술적 취약점 분석 평가 방법 상세 가이드 기준의 UID/GID가 0인 사용자 존재 여부만 체크하는 스크립트이다.
리눅스 서버에서 실행할 보안성 검토 스크립트는 사용자의 입맛에 맞게 변경하면 되며, 유의해야할 점은 사용자와 상호 작용할 구문이 있으면 안 된다.
예를 들면, echo -n "사용자 질문" 또는 read 구분이 있으면 expect 스크립트에서 에러가 발생한다.
# server_vuln_check.sh #
#!/bin/sh
LANG=C
export LANG
TYPE="CENTOS"
HOST=`uname -mrs`
RESFILE=$TYPE"_"$HOST".txt"
echo "▶ US1-01^root 이외의 UID/GID가 0인 사용자 존재여부" >> $RESFILE
echo "■ 현황" >> >> $RESFILE
echo "☞ UID/GID 정보" >> $RESFILE
if [ `awk -F: '$3==0 { print $1 }' /etc/passwd | egrep -v "^root|^toor" | wc -l` -eq 0 ]
then
awk -F: '$3==0 { print $0 }' /etc/passwd >> $RESFILE
echo "root 이외의 UID가 0인 사용자가 존재하지 않음" >> $RESFILE
echo "양호" >> $RESFILE
else
awk -F: '$3==0 { print $0 }' /etc/passwd | egrep -v "^root|^toor" >> $RESFILE
echo "root 이외의 UID가 0인 사용자가 존재" >> $RESFILE
echo "취약" >> $RESFILE
fi
main_shell_script.sh : 메인 역할
main_shell_script.sh는 진단할 서버 정보 및 expect로 구현된 스크립트를 로드하는 메인 역할을 하는 스크립트이다. 코드를 보면 크게 5가지로 구성되며 각 역할을 다음과 같다.
1. server.txt 파일에 저장된 정보만큼 반복하여 아이피 주소, 사용자의 아이디, 사용자의 패스워드, root의 패스워드를 각 변수에 저장
2. expect server_put.exp는 일반 사용자의 권한으로 진단할 서버의 /tmp 디렉토리로 server_vuln_check.sh 파일을 업로드
3. expect server_execute.exp는 root로 전환하여 server_vuln_check.sh 파일을 실행
4. expect server_get.exp는 진단 완료한 결과물을 로컬 리눅스 서버의 res 디렉토리로 업로드
5. expect server_delete.exp는 진단할 서버에 생성한 /tmp 디렉토리의 파일을 삭제
# main_shell_script.sh #
#!/bin/sh
LANG=C
export LANG
for i in `cat server.txt`
do
# 진단할 서버 정보
ipaddr=`echo $i | awk -F "[~]" '{print $1}'`
userid=`echo $i | awk -F "[~]" '{print $2}'`
userpw=`echo $i | awk -F "[~]" '{print $3}'`
rootpw=`echo $i | awk -F "[~]" '{print $4}'`
# server_vuln_check.sh 스크립트 업로드
expect server_put.exp $ipaddr $userid $userpw
echo "@ $ipaddr : 스크립트 업로드 성공!!!"
echo ""
# server_vuln_check.sh 스크립트 실행
expect server_execute.exp $ipaddr $userid $userpw $rootpw
echo "@ $ipaddr : 스크립트 실행 성공!!!"
echo ""
# 생성된 결과물을 로컬 리눅스로 업로드
expect server_get.exp $ipaddr $userid $userpw
echo "@ $ipaddr : 결과물 다운로드 성공!!!"
echo ""
# 진단 서버에 생성된 파일 삭제
expect server_delete.exp $ipaddr $userid $userpw $rootpw
echo "@ $ipaddr : 업로드된 파일 삭제 성공!!!"
echo ""
done
server_put.exp : 스크립트 업로드
server_put.exp는 scp 명령어를 이용해 일반 사용자의 권한으로 /tmp 디렉토리에 원본 스크립트를 업로드한다. 최초 scp 명령어를 실행하면 사용자에게 "yes/no", "password"를 요구하므로 사용자에게 출력할 문구를 작성하면 이에 대한 응답으로 지정한 매개변수 값을 자동으로 실행한다.
리눅스, 유닉스 종류에 따라 scp 명령어로 실행했을 때 출력되는 문구가 다르다. 이를 해결하려면 if 구문으로 모든 서버에서 출력되는 문구를 정의하거나 동일한 키워드를 삽입하면 된다.
# server_put.exp #
#!/usr/bin/expect
set ipaddr [lindex $argv 0]
set userid [lindex $argv 1]
set userpw [lindex $argv 2]
set timeout 10
spawn scp server_vuln_check.sh $userid@$ipaddr:/tmp/
expect {
"yes/no" {send "yes\r";exp_continue}
"password" {send "$userpw\r"}
}
expect eof
server_execute.exp : 스크립트 실행
server_execute.exp는 ssh 명령어로 일반 사용자로 접속을 하고, su - 명령어로 root로 전환한다. 이후 /tmp 디렉토리에 존재하는 원본 스크립트의 권한을 777로 변경 후 스크립트를 실행한다.
코드를 보면 중간에 *password* 문자열이 존재하는데, 이는 사용자에게 출력할 문구 중 password 문자열이 존재하면 일반 사용자의 패스워드 값을 전송하라는 의미이다.
# server_execute.exp #
#!/usr/bin/expect
set ipaddr [lindex $argv 0]
set userid [lindex $argv 1]
set userpw [lindex $argv 2]
set rootpw [lindex $argv 3]
set timeout 10
spawn ssh $userid@$ipaddr
expect {
"yse/no" {send "yes\r";exp_continue}
"*password*" {send "$userpw\r";exp_continue}
"*from*" {send "su - root\r"}
}
expect "Password"
send "$rootpw\r"
expect "*]#"
send "cd /tmp\r"
send "chmod 777 /tmp/server_vuln_check.sh\r"
send "sh /tmp/server_vuln_check.sh\r"
send "exit\r"
send "exit\r"
interact
server_get.exp : 진단 결과물 다운로드
server_get.exp는 진단할 리눅스 서버에서 스크립트 실행이 완료되면 결과물인 텍스트 파일이 저장된다. 텍스트 파일의 형식은 CENTOS_호스트명.txt 이므로 /tmp/CENTOS* 형식의 파일을 로컬 리눅스의 /res 디렉토리에 저장한다.
# server_get.exp #
#!/usr/bin/expect
set ipaddr [lindex $argv 0]
set userid [lindex $argv 1]
set userpw [lindex $argv 2]
set timeout 10
spawn scp $userid@$ipaddr:/tmp/CENTOS_* ./res/
expect {
"password" {send "$userpw\r"}
}
expect eof
server_delete.exp : 생성한 파일 삭제
server_delete.exp는 취약점 진단이 완료되었으므로 진단 대상 서버에서 생성한 파일(server_vuln_check.sh, CENTOS_*.txt)을 삭제해야 한다. ssh 명령어로 일반 사용자로 로그인 후 root로 전환하여 /tmp 디렉토리에 생성한 모든 파일을 삭제하고 쉘을 종료한다.
# server_delete.exp #
#!/usr/bin/expect
set ipaddr [lindex $argv 0]
set userid [lindex $argv 1]
set userpw [lindex $argv 2]
set rootpw [lindex $argv 3]
set timeout 10
spawn ssh $userid@$ipaddr
expect {
"password" {send "$userpw\r";exp_continue}
"*from*" {send "su - root\r";exp_continue}
"*assword*" {send "$rootpw\r";exp_continue}
}
expect "*]#"
send "rm -f /tmp/CENTOS_* /tmp/server_vuln_check.sh\r"
send "exit\r"
send "exit\r"
interact