제목 : Jumpserver 원격 실행 취약성 분석 및 재생산

0 简介​

Jumpserver는 4A 사양을 준수하는 운영 및 유지 보수 보안 감사 시스템 인 오픈 소스 요새 기계입니다. Layman의 용어로는 스프링 보드 머신입니다.
2021 년 1 월 15 일, Jumpserver는 보안 업데이트를 발표하여 원격 명령 실행 취약점을 수정했습니다. 일부 Jumpserver 인터페이스에는 승인 제한이 없으므로 공격자는 악의적 인 요청을 구축하여 민감한 정보를 얻거나 관련 작업을 수행하여 모든 기계를 제어하고 임의의 명령을 실행할 수 있습니다.
영향 버전 :
Jumpserver v2.6.2jumpserver v2.5.4jumpserver v2.4.5jumpserver=v1.5.9

1. 漏洞分析​

수리 코드의 커밋 레코드를 참조하십시오 : https://github.com/jumpserver/jumpserver/commit/f04e2fa0905a7cd439d7bc81089439d7bc8104e.
CeleryLogwebsocket 클래스의 연결은 ID 인증과 함께 추가 된 것으로 밝혀졌습니다.
수입 시간
OS 가져 오기
스레딩 가져 오기
JSON 수입
common.utils에서 get_logger를 가져옵니다
.celery.utils에서 가져 오기 get_celery_task_log_path
.ansible.utils import get_ansible_task_log_path
channels.generic.websocket import jsonwebsocketconsumer
logger=get_logger (__ name__)
클래스 TaskLogwebSocket (JSONWEBSOKETCONSUMER) :
연결이 끊어졌습니다=false
log_types={
'celery': get_celery_task_log_path,
'Ansible ': get_ansible_task_log_path
}
DEF CONNECT (SELF) :
user=self.scope [ '사용자']
user.is_authenticated and user.is_org_admin: 인 경우
self.accept ()
else:
self.close ()
def get_log_path (self, task_id) :
func=self.log_types.get (self.log_type)
func: 인 경우
return func (task_id)
def arever (self, text_data=none, bytes_data=none, ** kwargs) :
data=json.loads (text_data)
task_id=data.get ( 'task')
self.log_type=data.get ( 'type', 'celery')
task_id: 인 경우
self.handle_task (task_id)
def wait_util_log_path_exist (self, task_id) :
log_path=self.get_log_path (task_id)
self.disconnected:이 아닙니다
os.path.exists (log_path) :이 아닌 경우
self.send_json ({ 'message':'. ','task': task_id})
Time.sleep (0.5)
계속 계속하십시오
self.send_json ({ 'message':'\ r \ n '})
try:
logger.debug ( '작업 로그 Path: {}'. 형식 (log_path))
task_log_f=Open (log_path, 'rb')
Task_Log_f를 반환합니다
OSERROR:을 제외하고
반환 없음
def read_log_file (self, task_id) :
task_log_f=self.wait_util_log_path_exist (task_id)
Task_Log_F:이 아닌 경우
logger.debug ( '작업 로그 파일은 없음 : {}'. 형식 (task_id))
반품
task_end_mark=[]
self.disconnected:이 아닙니다
data=task_log_f.read (4096)
데이터 : 인 경우
data=data.replace (b '\ n', b '\ r \ n')
self.send_json (
{ 'message': data.decode (errors='ingore '),'task': task_id}
))
data.find (b'succeeded in ')!=-1:
task_end_mark.append (1)
if data.find (bytes (task_id, 'utf8'))!=-1:
task_end_mark.append (1)
elif len (task_end_mark)==2:
logger.debug ( 'task log end: {}'. 형식 (task_id))
부서지다
Time.sleep (0.2)
task_log_f.close ()
def handle_task (self, task_id) :
logger.info ( 'task id: {}'. 형식 (task_id))
Thread=Threading.thread (target=self.read_log_file, args=(task_id,))
Thread.start ()
DEF DERPONTECT (self, close_code) :
self.disconnected=true
self.close ()
이 클래스의 HTTP 인터페이스를 확인하십시오.
drlkt2kjibk12525.png

이 클래스를 통해이 인터페이스의 액세스 체인은 다음과 같습니다.
ws/ops/tasks/log/방문/
- tasklogwebsocket 클래스의 수신 함수를 입력하십시오
- TaskLogwebsocket 클래스의 handle_task 함수를 입력하십시오
- TaskLogwebsocket 클래스의 read_log_file 함수를 입력하십시오
- tasklogwebsocket 클래스의 Wait_util_Log_Path_Exist 함수를 입력하십시오
- TaskLogwebsocket 클래스의 read_log_file 함수를 입력하십시오
-app/ops/utlss.py
h0dqrfe553d12526.png
에 get_task_log_path 함수를 입력하십시오
TaskId는 우리가 보낸 Text_data에서 구문 분석 할 수 있으므로 제어 할 수 있습니다. 다음 방법을 통해 로그 파일 /opt/jumpserver/logs/jumpserver.log를 읽을 수 있습니다.
WS: //10.10.10.10.10:8080/ws/ops/tasks/log/로 보내기
{ 'task':'/opt/jumpserver/logs/jumpserver '} 위의 것은 파일 판독의 원리입니다. 로그 파일 읽기에는 다음과 같은 제한 사항이 있습니다.
파일은 절대 경로를 사용 하여만 읽을 수 있습니다. 로그로 끝나는 파일 만 읽습니다. 다음 분석은 원격 코드 실행을 구현하는 방법입니다.
/opt/jumpserver/logs/gunicorn.log를 읽으면 운이 좋으면 사용자 UID, 시스템 사용자 UID 및 자산 ID를 읽을 수 있습니다.
사용자 idasset idsystem 사용자 ID 위의 세 가지 정보는 사용자가 로그에서 웹 터미널에 로그인하는 것을 찾아야합니다. 그것을 얻은 후. /API/V1/Authentication/Connection-Token/Interface를 통해/앱/인증/API/USERCONNECTIONTOKENAPI를 입력 할 수 있습니다
enckl3bnqsx12527.png

xc0acfcdf0212528.png

20 초 유효 기간 만있는 토큰은 user_id asset_id system_user_id를 통해 얻을 수 있습니다. 이 토큰은 Koko 구성 요소의 tty를 만드는 데 사용될 수 있습니다.
-https://github.com/jumpserver/koko/blob/4258b6a08d1d3563437ea2257ece05b22b093e15/pkg/httpd/webserver.go#l167 특정 코드는 다음과 같습니다.
5t4nqunwram12529.png

kj3vshgcc1c12530.png

완전한 RCE 이용 단계는 다음과 같이 요약됩니다.
WebSocket 연결은 승인없이 설정할 수 있습니다. 로그 파일은 WebSocket을 통해 읽어서 로그 파일의 시스템 사용자, 사용자 및 자산 필드를 얻을 수 있습니다. 3의 필드를 통해 토큰을 통해 20 초의 토큰을 얻고 Koko Tty에 들어갈 수 있습니다. 명령 실행

2 漏洞复现​

2.1 环境搭建​

로컬 환경 : Xubuntu20.04JumpServer 버전 : 2.6.1 버전 설치 단계 :
# 다운로드
git 클론 https://github.com/jumpserver/installer.git
CD 설치 프로그램
# 국내 Docker 소스 가속도
Docker_image_prefix=docker.mirrors.ustc.edu.cn을 내보내십시오
# Dev 버전을 설치 한 다음 2.6.1로 전환합니다 (2.6.1을 직접 설치할 수 있어야합니다. 처음에는 기본 DEV 버전으로 설치되었지만 중요하지 않음).
Sudo Su
./jmsctl.sh 설치
./jmsctl.sh 업그레이드 v2.6.1
# 시작합니다
./jmsctl.sh 전체 로그를 다시 시작합니다
# yanq @ yanq-desk in ~/gitrepo [22:18:53] C:127
$ git 클론 https://github.com/jumpserver/installer.git
'설치자'로 복제 .
Remote: 열거 개체 : 467, 완료.
Remote: 총 467 (델타 0), 재사용 0 (델타 0), 팩 반영 467
: 100% (467/467), 95.24 Kib | 182.00 kib/s, 완료.
프로세스 : 100% (305/305), 완료.
# yanq @ yanq-desk in ~/gitrepo [22:20:27]
$ CD 설치 프로그램
# yanq @ yanq-desk in ~/gitrepo/git:master o [22:20:30]
$ ls
config-example.txt config_init jmsctl.sh readme.md scripts static.env utils를 작성하십시오
# yanq @ yanq-desk in ~/gitrepo [22:18:59]
$ docker_image_prefix=docker.mirrors.ustc.edu.cn
# yanq @ yanq in ~/github/instaler on git:master o [22336003:43] c:130
$ Sudo Su
root@yanq:/home/yanq/github/installer# ./jmsctl.sh 설치
██╗██╗ ██╗██╗ ███╗██████╗ ██╗███╗ ██╗ ██╗███████╗██████╗ ██╗███████╗██████╗
██║██║ ██║██║ ██╔════════██╗
██║██║ ██║██║ ██████╔╝██║ ██║██╔████╔██║██████╔╝███████╗█████╗ ██████╔╝
█████████████║╚██║╚██╔╝██═══╝ █████████████║╚██║╚██╔╝██═══╝ ██╔═══╝ ╚════██╔══╝ ██╔══╝ ██╔═══█╗ ██╔═══█╗
╚█████╔╝╚██████╔╝██║ ╚█████╔╝╚██████╔╝██║ ██║██║ ╚═╝ ╚═╝ ██║ ╚████╔╝ ███████╗██║ ██║ ██║
╚════╝ ╚════╝ ╚════╝ ╚═╝ ╚═══╝ ╚═╝ ╚══╝ ╚══╝ ╚═══╝ ╚══╝ ╚══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
버전 : Dev
1. 점프 서버를 구성하십시오
1. 구성 파일을 확인하십시오
각 구성 요소는 Yaml 형식 대신 환경 변수 구성 파일을 사용하며 구성 이름은 이전 이름과 일치합니다.
구성 파일 위치 :/opt/jumpserver/config/config.txt
마치다
2. NGINX 인증서를 구성하십시오
인증서 위치는 :/opt/Jumpserver/Config/Nginx/Cert입니다
마치다
3. 백업 구성 파일
/opt/jumpserver/config/backup/config.txt.2021-01-17_22-03-52로 백업
마치다
4. 네트워크를 구성합니다
IPv6을 지원해야합니까? (y/n) (기본값은 n) : n
마치다
5. 암호화 키를 자동으로 생성합니다
마치다
6. Persistence 디렉토리를 구성하십시오
통나
설치 후 :을 변경할 수 없습니다. 그렇지 않으면 데이터베이스가 손실 될 수 있습니다.
파일 시스템 용량 사용 사용 가능한 % 마운트 포인트
UDEV 7.3G 0 7.3G 0% /dev
/dev /nvme0n1p2 468g 200g 245g 45% /
/dev/loop1 56m 56m 0 100%/snap/core18/1944
/dev/loop2 65m 65m 0 100%/snap/gtk-common-temes/1513
/dev/loop3 218m 218m 0 100%/snap/gnome-3-34-1804/60
/dev/loop0 56m 56m 0 100%/snap/core18/1932
/dev/loop5 32m 32m 0 100%/snap/snapd/10492
/dev/loop6 65m 65m 0 100%/snap/gtk-common-temes/1514
/dev/loop4 52m 52m 0 100%/snap/snat-store/498
/dev/loop7 52m 52m 0 100%/Snap/Snap-Store/518
/dev/loop8 219m 219m 0 100%/snap/gnome-3-34-1804/66
/dev/loop9 32m 32m 0 100%/snap/snapd/10707
/dev/nvme0n1p1 511m 7.8m 504m 2%/boot/efi
영구 볼륨 스토리지 디렉토리 설정 (기본값 /opt /Jumpserver) :
마치다
7. MySQL 구성
외부 MySQL 사용 여부 (y/n) (기본값은 n) : n
마치다
8. Redis를 구성하십시오
외부 redis (y/n) 사용 여부 (기본값은 n) : n
마치다
2. Docker를 설치하고 구성하십시오
1. Docker를 설치하십시오
Docker 프로그램 다운로드 시작 .
-2021-01-17 22336004:12-- https://mirrors.aliyun.com/docker/ce/linux/static/stable/x86_64/docker-18.2-ce.tgz
호스트 거울을 해결합니다 .Aliyun.com (mirrors.aliyun.com) . 180.97.148.110, 101.89.125.248, 58.216.16.38,
연결 거울을 연결합니다 .Aliyun.com (mirrors.aliyun.com) | 180.97.148.110 | :443 . 연결.
HTTP 요청이 발행되었고 응답을 기다리고 있습니다 . 200 OK
길이 : 43834194 (42m) [Application/X-Tar]
: "/tmp/docker.tar.gz"절약
/tmp/docker.tar.gz 100%[=============================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================
2021-01-17 22336004:16 (13.8 MB/s)-저장된 "/tmp/docker.tar.gz"[43834194/43834194]).
Docker Compose 프로그램 다운로드 시작 .
-2021-01-17 22336004:17-- https://get.daocloud.io/docker/compose/releases/download/1.27.4/docker-compose-linux-x86_64
호스트 해결 get.daocloud.io (get.daocloud.io) . 106.75.86.15
연결 get.daocloud.io (get.daocloud.io) | 106.75.86.15 | :443 . 연결.
HTTP 요청이 발행되어 응답을 기다리고 있습니다 . 302 발견
위치 : https://dn-dao-github-mirror.daoclo...s/download/1.27.4/docker-compose-linux-x86_64 [New URL에 따라]
-2021-01-17 22336004:28-- https://dn-dao-github-mirror.daoclo...s/download/1.27.4/docker-compose-linux-x86_64
호스트 해결 dn-dao-github-mirror.daocloud.io (dn-dao-github-mirror.daocloud.io) . 240e3:964333333333333333333:33603fe, 61.160.204.242,
DN-DAO-Github-Mirror.daocloud.io 연결
HTTP 요청이 발행되었고 응답을 기다리고 있습니다 . 200 OK
길이 : 12218968 (12m) [application/x-executable]
: "/tmp/docker-compose"로 절약
/tmp/docker-compose
 
뒤로
상단