CTF Writeup: CICD GOAT | Part 2

banner1.png

We already covered easy challenges in Part 1, In this part we are going to tackle moderate ones so if you have not read Part 1 of this writeup you can read it here

Challenges - Moderate

Caterpillar

Description

chl4_desc.png

Hint 1: Fork the repository and create a pull request to trigger a malicious pipeline.
Hint 2: After you execute your malicious code in the Jenkins job, what environment variables can help you move forward?
Hint 3: Found that Gitea access token from the pipeline? Great. There’s another pipeline which is triggered by pushing to the main branch. Maybe you can access the flag from there!

Jenkinfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
pipeline {
agent any
environment {
PROJECT = "loguru"
}


stage ('Print Environment Variables') {
steps{
// Print all environment variables
sh 'printenv'
}
}


stage ('Lint') {
steps {
sh "pylint ${PROJECT} || true"
}
}

stage ('Unit Tests') {
steps {
sh "pytest || true"
}
}

stage('deploy') {
when {
expression {
env.BRANCH_NAME == 'main'
}
}
steps {
withCredentials([usernamePassword(credentialsId: 'flag2', usernameVariable: 'flag2', passwordVariable: 'TOKEN')]) {
sh 'curl -isSL "http://wonderland:1234/api/user" -H "Authorization: Token ${TOKEN}" -H "Content-Type: application/json" || true'
}
}
}
}

post {
always {
cleanWs()
}
}
}

After cloning the repository i tried to poison the flag2 i.e. sh 'echo "${TOKEN}" | base64' but we dont have persmission to modify the Jenkinfile as Description also says we don’t have write permission but only read

And following Hint1 i forked the repository with default user/dev alice and modified the pipeline script

print_token.png

and it indeed trigger the pipeline but no result in hand

no_flag.png

i tried multiple time thinking i am messing up with groovy syntax or what not, but i ignored something in Jenkinfile i.e. there is a condition before credential binding code in deploy stage i.e.

1
2
3
4
when {
expression {
env.BRANCH_NAME == 'main'
}

so its basically preventing us to build deploy phase as our PR is not from main branch so we have to amend it(just delete the condition) and send PR again

no_flag2.png

Could not find credentials entry with ID ‘flag2’ hmm, or it might be there is flag2 present there but because branch security we are not able to get it and after looking at Jenkin it got clear that there is two environment for pipeline i.e.

two_env.png

and its cruical security step for branch security often most pipelines have several environments. Each of these environments has a specific use case, and their security posture often differs.

and here we have test and prod, where test is used to test the application or select features before they are pushed to production and prod is the PROD environment is the most sensitive. This is the current active environment that serves users or customers. To ensure that our users have the best experience, this environment must be kept stable. No updates should be performed here without proper change management.

pipeline_env.png

if you have gone through the path/materials i shared in Part 1 you must be knowing about it or else you can learn more about pipeline environment and automation you can refer this THM lab.

Okay so in a nutshell wonderland-caterpillar-test is used to assess incoming PRs and do not posses priviledges to see the credentials

so we have to somehow to trigger the pipeline that run prod environment and for that we need higher priviledge i.e. main, controller here.

after wondering little bit Hint 2 got my attention that says After you execute your malicious code in the Jenkins job, what environment variables can help you move forward?

there must be something with environment variables, We can print all env vars for current runner i.e. test

print_envs.png

Initially i added the command in deploy staget that end up giving me result so i placed it before deploy

and after pipeline triggered we got this result

gitea_token.png

and it took my attention ‘cause Hint 3 mention about Gitea access token

well no we have acess token that is being use for several purpose in pipeline but no limit to:

1. Authentication and Authorization
  • Secure Access to Repositories: Use the Gitea token to authenticate and authorize access to repositories, ensuring that only authorized users and services can interact with your code repositories during the CI/CD process.
  • Limited Scope Tokens: Generate tokens with limited scopes to minimize the permissions granted. For example, a token might only have read access to repositories, preventing any write operations.

and i asked to chatGPT how acess token in pipeline security is used and one of the usage was like this

gpt_ans.png

So most probably, this PAT (Personal Access Token) belongs to a higher privileged user who has write permissions on the branch, as required by the pipeline.

so lets clone the repo and using PAT and try to modify thes Jenkinfile for accessing TOKEN i.e. flag2 and see if it works

print_token.png

git_commit1.png

and indeed we did able to so trigger the pipeine and got the flag!

chl4_flag.gif

Learning Takeaway

1. Public-PPE (3PE)

3PE.png

Unlike White Rabbit and Mad Hatter this time we dont have direct or indirect write access to repository but we did able to modify jenkinfile and trigger pipeline by forking it where good protection would have been to run the jekinfile from the specified branch(main)/repository here not from the unkown forked PR and end up escalting our priviledge.

A good example of this attack - 3PE found in PyTorch machine learning library that was huge supply chain attack.

Here are misconfiguration that was in place and that can mitigated using follwoigng solutions

  1. Here secret were exposed to PRs from forks
    Mitigation - Prevent access to secrets from forks

  2. Access token is set as environment variable on an agent and accessible for all jobs
    Mitigation - Limit access to secrets as much as possible

  3. Acess token had write permission
    Mitigation - Don’t create access tokens with write permission if there is no need for it.

  4. No review required before pushing to main
    Mitigation - Configure branch protection rules to require reviews.

Cheshire Cat

Description

chl5_desc.png

Hint1 : Try executing a Direct-PPE attack.
Hint2 : How can a Jenkinsfile instruct Jenkins to run the job on the Controller?
Hint3 : Try finding the label of the Controller - the “Built-In Node”.

Its clear from description that we have to execute code on the Jenkins Controller(master) to get the flag5 by using D-PPE(from Hint1).

so cloned the repository poison the pipleline by adding following script

1
2
3
4
5
6
7
8
9
10
11
12
13
pipeline {
agent { label 'master' } // This specifies the job will run on the Jenkins Controller
stages {
stage('Read and Print File') {
steps {
script {
def flagContent = sh(script: 'cat ~/flag5.txt', returnStdout: true).trim()
echo "The content of ~/flag5.txt is: ${flagContent}"
}
}
}
}
}

and PR that tigger the pipeline but we instead flag we got this

commit3.png

so i changed label to controller

commit1.png

and that didn’t work as well, and doing some google search i got this

jenkin_build-in.png

so we have to change the lable to built-in

1
2
3
4
5
6
7
8
9
10
11
12
13
pipeline {
agent { label 'built-in' } // This specifies the job will run on the Jenkins Controller
stages {
stage('Read and Print File') {
steps {
script {
def flagContent = sh(script: 'cat ~/flag5.txt', returnStdout: true).trim()
echo "The content of ~/flag5.txt is: ${flagContent}"
}
}
}
}
}

and we got the flag! ezz.

chl5_flag.png

Learning Takeaway

1. D-PPE

2. CICD-SEC-5: Insufficient PBAC (Pipeline-Based Access Controls)

IPBAC.png

Mitigations

IPBAC_Mitigation.png.png

Twiddledum

Description

chl6_desc.png

Hint1 : What dependencies are used by the twiddledum app?
Hint2 : The twiddledee package is a dependency. Use it to execute malicious code in the twiddledum pipeline.

By reading the description and Hint i got some intuition about this challenge as i have studied about this one while completing THM DevSecOps path.

Okay, so most probably Twiddledum repo using dependency and we can see that here

depedency.gif

and we have access to that dependecny repo i.e.

repos.png

and as we have don not have Jenkinfile in any of these repo i am assuming it will tigger automatically after changes made to the repos?

as we have access to Jenkins we can confirm if any pipeline there and indeed there was

twiddle_pipe.png

hmm, so now have dependency that being used by project repository and we have pipeline in jenkins all we have to do is somehow poision or inject our code in the dependency that cause pipeline to trigger therefore so does our malicious code

but the question is where to place the code? as we are not aware which part of code/file we should be targeting for.

maybe index.js that is from the intuition of index.html is used to serves as the home page for a website.

and even after looking for it i finally got clear that we have to target index.js e.g.

package.json file

index1.png

and running pipeline manually

index.gif

so like last challenge i tried to print all the environment variable from the agent

index2.png

and i waited for long but pipeline did not trigger, but why?

So theory behind it is - I asked to chatGPT as i am bit tiered :P

Changing the dependency version to a higher number can make a dependency confusion attack successful due to the way many package management systems resolve and prioritize dependencies. Here’s a breakdown of why this happens:

Package Resolution Logic

  1. Version Preference: Many package managers (like npm, pip, etc.) are configured to prefer the latest version of a package. When a build system or developer requests a dependency, the package manager will often fetch the highest available version that satisfies the dependency requirements.

  2. External Repositories Priority: In some cases, package managers might prioritize fetching packages from external repositories over internal ones unless explicitly configured otherwise. This is because external repositories are typically assumed to be the primary source of packages.

Attack Scenario

  1. Internal Dependency Name: Suppose an organization uses an internal package named example-package with version 1.2.3.

  2. Attacker’s Strategy: The attacker discovers the name of this internal dependency and publishes a malicious package with the same name, example-package, but with a higher version number, say 2.0.0, on a public repository like npm or PyPI.

  3. Version Resolution: When the organization’s build system runs and attempts to resolve example-package, it checks both the internal repository and the external public repository. Seeing the higher version number 2.0.0 from the public repository, the package manager might choose this version over the internal 1.2.3.

  4. Malicious Code Execution: The malicious version 2.0.0 gets installed, potentially executing harmful code or compromising the application, because it contains the attacker’s payload.

Example in Practice

Consider a simplified example with npm (Node Package Manager):

  • Internal Package: example-package version 1.2.3 hosted on a private registry.
  • Malicious Package: example-package version 2.0.0 hosted on the public npm registry.

If the package.json of the application specifies a dependency like this:

1
2
3
4
5
{
"dependencies": {
"example-package": "*"
}
}

Or even:

1
2
3
4
5
{
"dependencies": {
"example-package": "^1.0.0"
}
}

The package manager might fetch the higher version 2.0.0 from the public registry because it is the highest version available that meets the specified criteria. The build system could end up installing the attacker’s malicious package, leading to a successful dependency confusion attack :D.

dependency_c_dia.png

Image credit: TryHackMe

you can learn full attack from this THM Lab

so i released new version with higher number

new_version.gif

and after building pipeline manually we got the all env vars including flag

flag1.png

but redacted one that we can print by encoding it

index3.png

and after releasing new version, building pipeline we got the flag!

flag2.png

Learning Takeaway

[CICD-SEC-3: Dependency Chain Abuse](# CICD-SEC-3: Dependency Chain Abuse)

dependency_confusion_owasp.png

Mitigations

chl6_miti.png

Dodo

Description

chl7_desc.png

Hint1 : Read about Checkov, which is the scanner that stops you from making a mess.
Hint2 : Read about Malicious Code Analysis.

Our mission is to make the S3 bucket public-readable without getting caught, i looked at both hint where reading given aritcle about Malicious Code Analysis and checkov i understood my work.

So before going to solve the challenge lets get familiar with these tools:

  • Checkov : Checkov is an open-source static analysis tool designed to help developers and DevOps teams find and fix security and compliance issues in infrastructure as code (IaC). Developed by Bridgecrew, Checkov supports a variety of IaC frameworks including Terraform, CloudFormation, Kubernetes, ARM templates, and others. It scans IaC files to identify misconfigurations, potential vulnerabilities, and adherence to best practices.
  • Terraform: Terraform is an open-source infrastructure as code (IaC) tool developed by HashiCorp. It allows users to define and provision data center infrastructure using a high-level configuration language known as HashiCorp Configuration Language (HCL) or optionally JSON.

What we got from article - research?

Attackers can abuse misconfigurations in these tools to bypass security scans or even run malicious code on the CI system. This can be done by adding a configuration file(.checkov.yml) that tells the scanner to ignore certain rules or to execute custom code.

checkov_poison.png

So basically our mission is to make the S3 bucket public-readable that being created by terraform without getting caught from checkov.

i proceded by manually building pipeline in Jenkin

dodo_pipeline.png

where we got this output in console where in sage: Scan and Deploy checkov in charge going through specified rule.

checkov1.png

where Checkov instructed to scan the current directory (and its subdirectories) for IaC configurations and apply these specific AWS-related checks(CKV2_AWS_39,CKV2_AWS_38,CKV_AWS_20,CKV_AWS_57). The tool will analyze the configurations and report any findings related to the specified rules. This is useful for ensuring that your IaC code adheres to security best practices and compliance requirements, thereby reducing the risk of security vulnerabilities in your infrastructure.

checkov2.png

and file to be scan is File: /main.tf and code to scan lies within 112-132 lines i.e.

main_tf.png

here we chekcout guide reference given in console result where specific misconfiguration is being checked for.

guide_link.gif

where Arguments: acl suppose to be private as its good practice

1
2
3
4
5
resource "aws_s3_bucket_acl" "data" {
bucket = aws_s3_bucket.private_acl_v4.id
- acl = "public-read"
+ acl = "private"
}

to better understand AWS s3 bucket configuration file check here

So now what we have to do is just change the acl value to public-read from private in main.tf file and create a file .checkov.yaml in dodo parent directory and ofcourse we know the reason behind it, push the changes to main and ultimatley kick off build in Jenkins hoping for flag to print in the console!

chl7_flag.gif

Learning Takeaway

CICD-SEC-1: Insufficient Flow Control Mechanisms

IFC.png

Mitigation

IFC_Miti.png

Stay tuned last part yet to come :)