Lab 3: Software Supply Chain — Code Security
Warning
Due to a last minute change, the new url of the plaform is https://gitlab.tailccff78.ts.net/. Credentials are the same
For the same reason, refer to this other README to install the app.
Introduction
GitLab is a Git repository manager and DevOps platform with support for CI/CD pipelines.
You can explore GitLab’s full documentation here.
You will work with a Python-based application called cicdiaries, which is hosted in the cloud-security-lab GitLab repository.
To find it navigate to Projects > Member.
Note
Instructions on how to login to the platform should have been sent to you by email.
Task 1: Get Familiar with git, GitLab and the repository
Clone the Git repository and analyze it.
Follow the README.md and install the app.
Then create a new branch with the updated documentation on the python version: commit and push it. Finally create a merge request and merge the code into main.
Note
Only HTTPS is supported.
SSH access is disabled.
Warning
Some of you may not be able to install the app locally. This is because the developer did not clearly discuss the requirements of the app and specifically the python version that is 3.13.
Warning
Ensure you have disabled CI/CD pipelines for the project:
Go to Project's Settings >> General >> Visibility, project features, permissions
Expand the Repository section
Disable the CI/CD toggle
Then, click on Save Changes
Sign and Verify Commits
In this part, you will learn about signed and verified commits.
Task 2: Commit Changes as Someone Else
For this task, you have to leverage the fact that the Administrator does not have key verification enabled, allowing you to commit changes on their behalf.
As an example, create a new commit using the name
Administratorand the emailroot. Use "Malicious commit." as the commit message.
Question 1
What happened when you tried to push your commit?
Did it succeed?
Who was the author?
Can you explain, in your own words, the security implications of this scenario?
Task 3: Enable Signed Commits with GPG
In this part, you will set up a GPG signing key and enable it in GitLab and in your local repository. Instructions on how to achieve this are available here. A successful setup should lead you to a state similar to the one shown in the figure below:
Question 2
Now repeat the experiment from Task 2.
Do you notice any difference compared to the previous task?
Can you explain why?
How can the system verify the signature of a commit?
Tip
You can check signature information of commits using: git log --show-signature
Branch Protection
In this section, you will explore how branch protection rules can be used to enforce security practices in a GitLab repository. Branch protection helps prevent unauthorized changes and ensures that code changes go through proper review and verification processes.
Task 4: Configure Branch Protection Rules
- Navigate to your project in GitLab.
- Go to Settings > Repository > Protected branches.
- Protect the
mainbranch by enabling the following options: - Allow only maintainers to merge
- Prevent force push
- Allow no one push and merge
Question 3
How does enabling branch protection rules affect your ability to push changes directly to the main branch?
What are the benefits of enforcing such rules in a production environment?
Note
Remember that from now one, you can only merge changes to main via a merge request
Testing and Auditing
Code testing is an effective way to prevent errors in the source code.
Task 5: Fix a broken test
Use the command pytest (follow the instructions in the README file) to run the application tests. You should notice that one test is not passing.
Can you fix it?
Task 6: Test the Application for Security Issues
Testing is useful not only to verify the intended behaviour of an application but also to identify potential security issues. One of the most common threats in web applications is code or JavaScript injection. We can use testing to verify that our application is resilient to some of the most common injection attacks.
For instance, a malicious user or collaborator could inject malicious JavaScript or HTML into a blog post to, for example, trick visitors into clicking a malicious link. We can try to inject a simple script into the body of a blog post in a test and observe what happens.
Add the following test to app/tests/test_db.py, then answer the questions:
def test_blog_injection():
malicious_body = '<script>alert("hacked")</script><b>blogpost</b>'
post = BlogPost(
id=3,
title="Injection test",
body=malicious_body,
created_at=datetime(2025, 5, 10, 14, 30, 0)
)
serialized = post.serialize()
# We want to escape all HTML tags in the body and title.
assert "<script" not in serialized["body"]
Question 4
If you run the test, it will not pass.
What is the problem?
Which security property(ies) may be compromised by such an attack?
Can you find a countermeasure?
Task 7: SAST
The application uses the bandit tool which you are welcome to read and study. To run the security tests, execute the following command in the root folder:
Question 5
Did the tool find any security issues?
What does the output look like?
Can this be considered static or dynamic testing, and why?
Can you understand and fix the errors?
Credentials leak
The developer of the app once pushed an environment file which may contain important secrets under app/.env.
Question
Can you identify the commit in which that file was added?
(Optional) Fuzz testing
Fuzz testing consists in injecting random (or pseudo-random) inputs to a program. And our app needs a bit of fuzz testing too. As a main library, we are going to use Google's Atheris fuzzer.
You can install it by running pip install atheris
Note
MacOS users will need to install additional software as specified in the project's README.
You can also run:
Create a new file: app/fuzz_blog.py with the following content:
import atheris
import sys
import random
import re
from datetime import datetime
from atheris import FuzzedDataProvider
from website.models import BlogPost
SEED = 42
DANGEROUS_PATTERNS = [
r"<\s*script",
r"<\s*javascript:",
r"<\s*iframe",
r"<\s*img.*onerror",
# TODO: find other interesting patterns
]
XSS_PAYLOADS = [b"Add Meaningful payloads"]
def custom_mutator(data: bytes, max_size: int, seed: int) -> bytes:
random.seed(seed)
rand = random.random()
# ~20% of the times inject custom payloads
if rand < 0.2:
return random.choice(XSS_PAYLOADS)
# ~10% of the times return the original random data
elif rand < 0.3:
return bytes(data[:max_size])
else: # mutate existing input
data = bytearray(data)
# TODO: Implement mutation logic.
# Examples:
# - Random byte flip
# - Split XSS_PAYLOADS and append or prepend data
# - Add opening or closing tags < >
return bytes(data[:max_size])
def contains_xss(payload: str) -> bool: # This function checks for XSS
payload_lower = payload.lower()
return any(re.search(pattern, payload_lower) for pattern in DANGEROUS_PATTERNS)
def no_raw_html(output: str):
# Avoid checking for benign cases like "<3"
if "<" in output:
if not re.search(r"<[a-zA-Z!/]", output):
return
if "<" not in output:
raise AssertionError(f"Potential unescaped HTML: {repr(output)}")
@atheris.instrument_func
def TestHTMLInjection(data: bytes) -> None:
fdp = FuzzedDataProvider(data)
try:
body = fdp.ConsumeUnicode(1000)
except Exception:
return
post = BlogPost(
id=1,
title="title",
body=body,
created_at=datetime(2025, 5, 10, 14, 30, 0)
)
serialized = post.serialize()
# Validate all string fields, not just body
for field, value in serialized.items():
if not isinstance(value, str):
continue
# Pattern-based detection
if contains_xss(value):
raise AssertionError(f"XSS payload survived in field '{field}': {value}")
# Raw html content detection
no_raw_html(value)
def main():
random.seed(SEED)
args = sys.argv
if not any(a.startswith("-seed=") for a in args):
args = args[:1] + [f"-seed={SEED}"] + args[1:]
atheris.Setup(
args,
TestHTMLInjection,
custom_mutator=custom_mutator
)
atheris.Fuzz()
if __name__ == "__main__":
main()
To run the fuzzer go to the app folder and run python fuzz_blog.py -max_total_time=60
Question
Implement the custom_mutator function and add XSS_PAYLOADS. Then run the fuzzer. Do you find any error?
Try to fuzz the title field. What did you find?