This is part 2 of my blog series on abusing CICD platforms for red teaming, covering the moderate level challenges from CICD-Goat. I recommend reading part 1 first if you have not already!
The challenge for caterpillar states that we will only have read access to the repository and tells us to steal a credential from the Jenkins store. Looking at Gitea we can see the repository for the challenge.
We can see that a Jenkinsfile is in the repository and will be the most interesting file most likely.
In the above file we can see that the
deploy stage will execute only when the branch main is updated, and that if it was the main branch will load credentials into the pipeline and then execute a curl request with them. This is a common thing I see during Red Teams, with pipelines set to only run on main branches and then not having access direct to main.
To deal with this I will fork the repository to my own user.
On my own fork I can change the Jenkinsfile so that it will run on any branch and will print out the flag variable.
Then I can create a PR to the main repo.
With the PR in a build was seen in the Jenkins job
wonderland-caterpillar-test, which encountered an error for not having a secret with the id flag2.
So it seems that the
wonderland-caterpillar-test is used to assess incoming PRs, and
wonderland-caterpillar-prod would be the pipeline that would be able to extract the secret
flag2. With the direct access failing for the secret, I will pull back the environment variables for the runner of the
wonderland-caterpillar-test pipeline, since there was a reference earlier in the output to an access token for gitea.
To do this I will change the jenkins file to instead run the
env command. I place this earlier in the build stages since it is erroring on the
deploy stage so it is not executing my code.
Looking at the console output the environment variables can be seen for the runner, which included a variable
Personal Access Tokens (PATs) are very commonly used by pipeline runners to access repositories. This is often to read the repositories, but can be to push to them as well, especially in cases where the pipeline will, for example, increment a version number on the repo. In these cases, compromising the PAT would allow an adversary to write to the repo which they could previously not access.
To see what level of access we have to the repo with the token, we can clone the repo using the token.
git clone http://5d3ed5564341d5060c8524c41fe03507e296ca46@localhost:3000/Wonderland/caterpillar.git
I can then open it in VsCode and modify the Jenkinsfile and attempt to commit it back to main, which succeeds.
The jenkinsfile on the commit just printed out the TOKEN variable as we have done before.
This caused the jenkins job
wonderland-caterpillar-prod to run.
Looking at the output we can see the encoded value for TOKEN.
As with the tokens before, we can decode these and provide them to the CTFd scoreboard.
We can see from the challenge that we will need to get code execution on the jenkins controller rather than the dedicated nodes. We will start the chain looking at the Jenkinsfile to see what is available.
We can see from the file that the line
agent any is present, telling Jenkins to just use which ever runner is available. We can change this to declare a specific runner, however we require the label of the agent. This can be found for agent1, as seen below:
However, the built in node does not have a visible label in the GUI since it has no projects assigned. A bit of googling showed that the default label for the built in node was just
With the knowledge of the label we can edit the Jenkinsfile to force execution on the built in node and grab the flag file we need.
Be sure the put the malicious code at the top of the file since further stages will fail on the built-in node. Reviewing the output in Jenkins we can see the contents of the file.
This can then be submitted to CTFd to make sure it is correct.
The challenge to get Flag6 from the twiddledum pipeline abusing supposedly some form of logic.
Looking at the
twiddledum repos latest commit we can see a version increment for a package called twiddledee.
We have access to the
twiddledee repo and looking at the latest commit we can see a version increment.
Looking at the
package.json file that the above was contained in, we can see that the first step will be running the
index.js file. Looking inside that file we see the following:
Looking at Jenkins we can see the
wonderland-twiddledum pipeline. Neither of the repos have Jenkinsfiles, so this pipeline we need to manually trigger a build.
By triggering a build we can review the output and see the following line.
This is the output of what we saw in the
index.js file of the repo
twiddledee, showing that the code is getting executed by the pipeline and that we can see the output of the console.
With this knowledge, we will modify
index.js to output environment variables (since it is not pulling in secrets from jenkins).
We will then need to create a new release with a tag that will be loaded by Twiddledum, which should just take the latest as long as it is after 1.1.0.
Then we can go into Jenkins and manualy trigger another build and on the console log we can now see environment variables being output.
We can see that the variable
FLAG6 does exist and is being output, but it is being sanitised by Jenkins output log. We will get around this with some base64 encoding in
index.js and now that we know the name of the variable we can be more restrictive.
We can then set a new release with the tag
1.3.0 as we did before, then trigger another build within Jenkins to get the base64 output of the variables.
We can then decode this and submit it as usual.
After reading the challenge I decided to start by looking at the pipeline, and found it was another one where we could manually trigger builds, so I triggered a build to view the console output log before making any changes. We can see that Checkov is being used to monitor terraform files and ensure that public access is not given to S3 buckets.
We can see that various buckets will be created from the terraform stages.
Looking at the repository I didn’t see much that stood out and didn’t see a location in the repo where Checkov was being called, so it seemed to be a part of the pipeline stages that I could not control with my privilege. Doing some reading though about checkov, this article was found here which demonstrated that you could place a
.checkov.yml file pointing to a check that didn’t exist and it would essentially fail open the checks on the repo.
I took the file contents listed on the blog and added the
.checkov.yml file to the
Now that we should be able to bypass Checkov scanning, I reviewed the
main.tf file in the repo, which is the main terraform file that will be called to build resources. First we needed to find the definition of the dodo S3 bucket.
Then we could alter this to be
public-read`. If you do not know the terraform references, then the documentation is exceptionally useful and can be found here.
With the attack in place we could then trigger another manual build within Jenkins and at the bottom of the build we can see the flag.
This can then be checked by submitting it to CTFd.
Summary of Attacks
Through the moderate challenges we have seen some more complex cases of CICD abuse where we have needed multiple stages for attack.
- Extracting environment variables to get a PAT, allowing us to escalate privileges for the repo within gitea and then poison the pipeline directly. This is CICD-SEC-4 on the top 10 list.
- This attack could have been prevented by not having the PAT within the runners environment variables, especially since it did not appear to leverage it during the pipeline for write access. A seperate PAT could have been made with only read level access.
- Altering the jenkins pipeline to run on the built in node, which should never be used to run code due to the security risks, and using that access to read sensitive files off of the jenkins server file system. This is CICD-SEC-4 and CICD-SEC-5 on the top 10 list.
- This could have been prevented by stopping the built in node being used altogether, which is what Jenkins recommend is done with the built in node.
- By having write access to Twiddledee, we could abuse a dependency which would then later be executed by Twiddledum. This is CICD-SEC-3 on the top 10 list, Dependancy Chain Abuse.
- This is more difficult to prevent but it could have been mitigated by only allowing trusted members to push code to internal dependencies and having approval be necessary before publishing releases to be used.
- Abusing fail open configurations of static code checks to bypass Checkov scanning on the pipeline and deploy a publicly readable S3 bucket. This is CICD-SEC-1 on the top 10 list, Insufficient Flow Control Mechanisms.
- This potentially could have been prevented by having pre-commit checks that ensure a checkov config file is not being written to the repo, or ensuring that it is never in a fail open state.