SSH Tunnelling(SSH 터널링), PNT 그리고 리버스 쉘

오랜만에 글을 쓰는것 같다. 한동안 귀찮아서 미루어놓았었는데, 갑자기 글을 쓰고싶은 주제가 생겨 노트북을 열었다.

사고 분석을 하다보면, 또는 분석 보고서를 읽다보면 터널링(tunnelling), 리버스쉘(reverse shell), 피봇팅(pivotting) 같은 용어들을 자주 접할 수 있다.

아마 얼추 직감적으로 아 이게 어떤 의미고 무슨 기능이구나 하는 감각은 다들 가지고 있겠지만, 명확하게 정리된 글이 많이 없는 것 같아 필자가 준비 해 보았다.

순서대로 가 보자면, 피봇팅, 터널링, 리버스 쉘 순서로 가는게 맞는 것 같은데 사실 순서는 크게 상관없기 때문에 제목 순서대로 터널링 부터 얘기해보자.


1. SSH 터널링

아마 이 글을 읽는 독자분들, 여기까지 흘러들어온 여러분들 대다수는 이미 SSH가 뭔지 너무나도 잘 알것이라 생각한다.(예외도 있다. 작년에 필자가 WHS에서 가르쳤던 제자 중 수지라는 녀석이 SSH를 쓸 줄 모르는 것을 보고 충격받았다) 하지만 전체에서 대다수를 뼤고 수지같은 케이스도 있을것 같다는 생각이 들어 SSH에 대해서 먼저 짚고 넘어가보려 한다.


1-1. SSH

SSH란 Secure Shell의 약자로 쉽게 말하자면 TCP 소켓 통신 위에서 키 교환이라는 아주 독자적인 보안(암호화) 계층을 적용한 기술이라고 생각하면 좋겠다. 그래서 우리는 이 SSH를 사용해서 계정, 비밀번호 유출 걱정 없이 그리고 서버, 클라이언트 간 통신 내역 유출 걱정없이 원격에서 통신을 시도할 수 있다.

그리고 이를 응용하면, localhost 에서 어떤 포트와 포트 간에 정보를 전달하는 즉 local port forwarding, 또는 localhost와 remote host의 포트 간에 서로 데이터를 전달하는 remote port forwarding 등 기술에 응용할 수 있다.


1-2. Port Forwarding

자 벌써 어려운 용어가 하나 나와버렸다. 포트 포워딩이다. 여러분이 직접 서버를 운영 중이라거나, 또는 서버 구축을 해 보았거나, 그래서 공유기를 만져본 경험이 있다면 이 포트포워딩이라는 용어가 굉장히 익숙할 것이다.

이 포트포워딩이라는 것은 한 시스템의 특정 포트로 들어온 네트워크 트래픽을 다른 시스템이나 포트로 전달 하는 기술이라 이해하면 된다.

이 말인 즉슨, 현대의 OSI 7 Layer에서 4계층 TCP/IP 통신은 IP:Port 로 목적지를 식별한다. 즉 제대로된 통신을 수행하기 위해서는 IP와 Port가 둘 다 있어야 한다는 의미이며 따라서 최종적으로 Port에 의해 어떤 데이터가 어디로 전달되는지 결정된다.

그리고 이 포트는 0에서 65535까지 65536개가 존재하고, 이는 2의 16승이다.

아무튼, 이 포트포워딩이라는 기술을 사용하면 내 컴퓨터의 특정 포트 예를들면 2000번 포트로 들어온 데이터 스트림을 그대로 내 컴퓨터의 2001번 포트로 보낼 수도 있고, 또는 다른 컴퓨터의 31337 포트로도 전달할 수 있다.


1-3. 터널링(Tunnelling)

자 이제 우리는 ‘터널링’ 이라는 개념을 이해할 준비를 마쳤다. 터널링이라고 뭐 별게 있는 것이 아니다. SSH의 소켓통신과 포트포워딩 개념을 합치면 이것이 바로 터널링이 된다. 좀 더 전문적으로 얘기해보자면

SSH가 포트포워딩을 이용해 네트워크 트래픽(TCP 스트림)을 암호화된 경로로 캡슐화하여 다른 시스템으로 안전하게 전달하는 기술

이라고 설명할 수 있겠다. 아직까지도 뭔가 글로만 설명하니까 잘 와닿지 않을 수 있다. 그래서 필자가 그림을 그려서 설명해보겠다..


2. SSH 터널링 응용


2-1. Local Port Forwarding

로컬 포트 포워딩은 현재 내가 작업 중인 호스트가 터널링의 주체가 되는 것이다. 즉 이 경우에는 두 가지 케이스가 존재한다.

1. 로컬호스트의 특정 포트를 로컬 호스트의 또 다른 포트와 연결 2. 로컬 호스트의 특정 포트를 원격 호스트의 다른 포트와 연결

첫 번째 경우를 살펴보자. 로컬 포트포워딩의 경우 ssh -L 옵션을 사용한다. 전체 명령을 보고 자세히 뜯어보자

ssh -L <로컬_바인딩주소>:<로컬_포트>:<대상_호스트>:<대상_포트> <계정>@<SSH_중계서버>:<중계_포트>

구분설명
로컬 바인딩 주소내 PC가 바인딩할 IP 주소.
127.0.0.1 → 내 PC에서만 접근 가능
0.0.0.0 → 외부에서도 접근 가능
로컬 포트내 PC가 열어둘 포트 번호 (입구). 외부 클라이언트가 이 포트로 접속
대상 호스트SSH 서버가 트래픽을 전달할 목적지 호스트 (보통 SSH 서버 기준에서 접근 가능한 호스트)
대상 포트대상 호스트의 실제 서비스 포트 (예: 22, 80, 3306 등)
SSH 중계 서버SSH 터널을 실제로 생성할 중계 서버 (내가 SSH로 접속할 서버)

<로컬_바인딩주소>의 <로컬_포트>로 들어온 트래픽을 <SSH_중계서버>를 통해 <대상_호스트>:<대상_포트>로 암호화 전달한다.

쉽게 생각하면 <출발지>:<목적지> <경유지> 로 표현한다고 봐도 좋다.

여기서 <로컬 바인딩 주소> 는 생략이 가능하며 생략할 시 기본값으로 127.0.0.1이 된다.

또 생략된 모든 포트는 ssh 기본 포트인 22번 포트가 되며, 만약 22번 포트로 운용 중이 아니라면 그에 맞게 설정 해 주어야 한다.

1
2
3
4
5
6
7
8
9

# 로컬 호스트(127.0.0.1)만 접근가능한 2000 포트를 열고 여기로 들어오는 스트림은 모두 22번 포트로 전달하며 경유지는 로컬호스트다.
ssh -L 127.0.0.1:2000:localhost:22 localhost

# # 모든 호스트가 접근가능한 2000 포트를 열고 여기로 들어오는 스트림은 모두 22번 포트로 전달하며 경유지는 로컬호스트다.
ssh -L 0.0.0.0:2000:localhost:22 localhost

# 모든 호스트가 접근 가능한 2000 포트를 열고 이 포트로 접근하는 모든 스트림은 10.10.10.50을 거쳐 10.10.10.30:22로 향한다.
ssh -L 0.0.0.0:2000:10.10.10.30:22 id@10.10.10.50

2-2. Remote Port Forwarding

자 위에서 로컬 포트포워딩에 대한 예제는 충분히 들어주었다고 생각된다. 이제 원격 포트 포워딩을 알아보자

원격 포트포워딩의 일반적인 명령 구조는 다음과 같다.

ssh -R <원격_바인딩주소>:<원격_포트>:<대상_호스트>:<대상_포트> <계정>@<SSH_중계서버>:<중계_포트>

원격 포트 포워딩의 경우 다소 헷갈릴 수 있는데 행위의 주체를 잘 생각해야한다.

로컬 포트포워딩의 경우 행위의 주체가 내 서버였다. 하지만 원격 포트 포워딩의 경우 행위의 주체는 중계 서버가 된다.

SSH 서버(원격지)가 <원격_포트>를 열고, SSH 터널을 통해 <대상_호스트>:<대상_포트>로 연결. 즉 원격 입구 생성 라고 생각할 수 있으며

예제 명령은 다음과 같다.

1
2
3
4
5
6
7
8
9

# 로컬 호스트(localhost)서버가 로컬 호스트(127.0.0.1)만 접근가능한 2000 포트를 열고 여기로 들어오는 스트림은 모두 22번 포트로 전달.
ssh -R 127.0.0.1:2000:localhost:22 localhost

# # 로컬호스트 서버(localhost)가 모든 호스트가 접근가능한 2000 포트를 열고 여기로 들어오는 스트림은 모두 22번 포트로 전달.
ssh -R 0.0.0.0:2000:localhost:22 localhost

# 10.10.10.50 호스트에서 모든 호스트가 접근 가능한 2000 포트를 열고 이 포트로 접근하는 모든 스트림은 현재 작업중인 localhost를 거쳐 10.10.10.30:22로 향한다.
ssh -R 0.0.0.0:2000:10.10.10.30:22 id@10.10.10.50

2-3. 정리

정리하자면 다음과 같다.

구분명령 구조 (템플릿)설명
로컬 포트 포워딩ssh -L <로컬_바인딩주소>:<로컬_포트>:<대상_호스트>:<대상_포트> <계정>@<SSH_중계서버>:<중계_포트>내 PC(클라이언트)가 <로컬_포트>를 열고, SSH 중계서버를 통해 <대상_호스트>:<대상_포트>로 연결. 즉 로컬 입구 생성
원격 포트 포워딩ssh -R <원격_바인딩주소>:<원격_포트>:<대상_호스트>:<대상_포트> <계정>@<SSH_중계서버>:<중계_포트>SSH 서버(원격지)가 <원격_포트>를 열고, SSH 터널을 통해 <대상_호스트>:<대상_포트>로 연결. 즉 원격 입구 생성

구분명령 예제동작 요약
로컬 포트 포워딩ssh -L 0.0.0.0:2000:10.10.20.30:22 kali@10.10.20.50:22내 PC의 2000 포트를 열고, SSH 중계서버 10.10.20.50을 통해 내부망의 10.10.20.30:22 로 연결.
→ 외부에서 내PC:2000으로 접속하면 내부망 SSH로 터널링됨.
원격 포트 포워딩ssh -R 0.0.0.0:2000:localhost:22 kali@10.10.20.50:22원격 서버(10.10.20.50)가 2000 포트를 열고, 그 포트로 들어온 요청을 내 PC의 localhost:22로 전달.
→ 원격지에서 ssh -p 2000 localhost (서버 내)로 접속하면 내 로컬 SSH로 터널링됨.

다음 결과를 보면 좀 더 잘 이해가 될 지 모르겠다. 로컬에서 다음 두 명령을 실행한 후 포트 상태를 조회했다.

1
2
ssh -L localhost:2000:localhost:22 localhost
ssh -R localhost:2001:localhost:22 localhost

다음 그림을 보자

ssh port forwading

2000 포트는 ssh클라이언트가 바인딩을 하고있고, 2001 포트는 sshd, 즉 서버 데몬이 바인딩을 하고 있다.

즉 동작은 같지만 누가 포트 오픈과 바인딩의 주체가 되는가 하는 부분에서 차이가 발생한다.


3. PNT(Pivitting and Tunnelling)

자 지금까지 지식을 가지고 실제 펜테스팅에 사용되는 피보팅에 대해 생각해보자.

피봇이라는 건 어떤 회전의 중심 축을 의미한다. 아마 여러분들이 수학공부를 좀 했다면 Linear Algebra에서 대각 성분을 pivot이라 부른다는 걸 기억할 것이다.

자 그래서, 이 pentesting에서 pivot이라는 것은, 공격자가 획득한 초기 접근 지점을 통해 다른 내부 시스템으로 이동(Lateral Movement)하기 위해 트래픽을 우회하거나 프록시하는 기술 이라 정의할 수 있겠다.

이러한 피보팅에는 netcat, socat, chisel, frp, ssh, reGeorg, ngrok 등 다양한 도구를 사용할 수 있고, 이들 모두 원리는 비슷하다. 심지어는 웹 셸을 통한 리버스 커넥션도 가능하다.

아무튼, 뚫어놓은 노드를 거점으로 사용하는 것이다. Mitre ATT&CK Tactic으로 말하자면 Command and Control 정도 되지않을까.

이렇게 피봇팅을 사용하는 이유라면, 외부에서 내부망에 접근하려면 반드시 내부와 연결되는 접점을 통해야하고(외부에서 내부망으로 곧바로 접근할 수 없기 때문이다.), 침해 발생한 노드가 이러한 지점으로써의 역할을 하는 것이다.

또 다른 목적은 리버스 커넥션을 수행함으로써 공격자의 정보를 어느정도 숨길 수 있기 때문이다. 그리고 인바운드보다 아웃바운드에 대한 규제는 상대적으로 느슨하기 때문에 이를 악용하기 위함도 있다.


4. 리버스 쉘(Reverse Shell)

사이버 공격의 Life Cycle을 생각해보자. Lockheed Martin의 Cyber Kill Chain을 생각해보면 좀 더 직관적일 것 같은데

공격자는 최초 정찰을 통해 대상의 정보를 수집하거나, 인증 권한을 구매하는등 활동을 시작한다. 이후 제로데이, 피싱, drive by download 등 다양한 공격 벡터를 활용해 initial access 단계로 넘어간다.

초기 접근에 성공했다면 곧, Persistance 단계에 접어들 것이며 이후 필요하다면 Privilege Escalation 단계까지 진행할 것이다.

또 장악한 노드에 원하는 정보가 없다면 Lateral Movement를 통해 위상적으로 수평 이동을 시도 할 것이며 이 모든 과정에서 Defense Evasion 전략이 적용 될 수 있다.

일반적인 서버와 클라이언트 통신 과정을 생각해보자.

TCP/IP 프로토콜에서 서버와 클라이언트가 연결이 되면 어떤 IP가 어떤 포트를 사용중인지 확인할 수가 있다.

일반적일때 서버의 Listen 중인 서비스 포트에 대해 외부의 어떤 클라이언트가 접속해있는 형태다.

하지만 리버스 쉘 또는 리버스 커넥션은 이 반대가 된다. 서버가 역으로 클라이언트 쪽으로 접속 요청을 보내는 것이다.

다음 명령 실행 결과를 한 번 보자.

ssh 연결 시도

Ubuntu 서버에서 localhost의 7777 서버로 ssh 연결을 수행하자 갑자기 kali 서버로 로그인 되었다.

이게 어떻게 된 일일까? 칼리 서버에서 네트워크 정보를 확인해보자.


포트 확인

이상한 연결이 몇 개 보인다.

1
2
3
4
5
Active Internet connections (servers and established)
Local Address           Foreign Address  State        PID/Program name
127.0.0.1:60498         127.0.0.1:22     ESTABLISHED  3010314/ssh
127.0.0.1:22            127.0.0.1:60498  ESTABLISHED  3010580/sshd-sessio
176 10.10.20.50:46860   10.10.20.110:22  ESTABLISHED  3010314/ssh

바로 이것인데, 127.0.0.1 이라는 ip 주소에 대해 22포트와 60498 포트가 서로 바인딩 되어있고

현재 내 서버에서(10.10.20.50)에서 원격지(10.10.20.110)의 22번 포트로 연결이 성립되어있다.

그런데 실제로는 원격지(10.10.20.110)에서도 서버에서(10.10.20.50)에 들어와있다. 하지만 그런 정보는 어디에도 없다.

이것이 바로 ssh 터널링을 사용한 리버스 커넥션 공격이다.


5. 그외 다른 도구를 사용한 공격 방법들


5-1. netcat을 사용한 reverse connection

리버스 커넥션에는 ssh만 있는 것이 아니다.

정말 많이 사용하고 또 너무나도 유명한 netcat이 있다.

netcat


5-2. bash를 사용한 reverse connection

아니면 정말 심플하게 그냥 bash 기능으로도 연결이 가능하다.

bash


5-3. frp를 사용한 reverse connection

frp를 다운로드 받은 후 frp를 사용해서도 리버스커넥션을 해볼 수 있다. 이 frp는 특히 중국의 달빛(moonlight)라는 APT 그룹이 자주 사용하는 도구다

frp reverse shell


5-4. http 프로토콜을 사용한 reverse connection

당연하게도 웹 서버를 사용해서도 reverse shell 연결을 할 수 있다. 여기서는 간단하게 apache+php 환경으로 한번 해보겠다.

upload & nc listen

먼저 파일 업로드 취약점을 사용해 악성 파일을 업로드 했다고 가정하고(여기서는 http_proxy.php 다), kali 서버에서 포트를 열고 대기 시킨다.


execution

php 코드 실행을 위해 경로에 접근해준다.


connected

http 프로토콜을 사용해 리버스 쉘 연결이 맺어진 것을 볼 수 있다. 당연하게도 계정은 www-data다.


5-5. powershell을 사용한 reverse connection

파워쉘은 나중에 추가 예정!

이번 포스팅은 여기까지