diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..f57994a57 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,27 @@ +## Goal + + + +## Changes + + + +- + +## Testing + + + +```text + +``` + +## Artifacts & Screenshots + + + +## Checklist + +- [ ] The title follows `feat(labN): `. +- [ ] No secrets or large temporary files are committed. +- [ ] `submissions/labN.md` exists. diff --git a/.github/workflows/lab1-smoke.yml b/.github/workflows/lab1-smoke.yml new file mode 100644 index 000000000..b1180ee7c --- /dev/null +++ b/.github/workflows/lab1-smoke.yml @@ -0,0 +1,32 @@ +name: Lab 1 smoke test + +on: + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + juice-shop: + runs-on: ubuntu-latest + services: + juice-shop: + image: bkimminich/juice-shop:v20.0.0 + ports: + - 3000:3000 + steps: + - name: Wait for Juice Shop + run: | + for attempt in {1..12}; do + if curl --silent --fail http://localhost:3000/rest/admin/application-version; then + echo "Juice Shop is ready on attempt ${attempt}." + exit 0 + fi + echo "Waiting for Juice Shop (${attempt}/12)..." + sleep 5 + done + + echo "Juice Shop did not return HTTP 200 within 60 seconds." + exit 1 diff --git a/submissions/assets/Task2-Description.png b/submissions/assets/Task2-Description.png new file mode 100644 index 000000000..953d808e2 Binary files /dev/null and b/submissions/assets/Task2-Description.png differ diff --git a/submissions/assets/lab1/Bonus-1-StarSimpleContainerAPI.png b/submissions/assets/lab1/Bonus-1-StarSimpleContainerAPI.png new file mode 100644 index 000000000..fcfd49966 Binary files /dev/null and b/submissions/assets/lab1/Bonus-1-StarSimpleContainerAPI.png differ diff --git a/submissions/assets/lab1/Bonus-2-StarDevSecOpsRepo.png b/submissions/assets/lab1/Bonus-2-StarDevSecOpsRepo.png new file mode 100644 index 000000000..60f5198cc Binary files /dev/null and b/submissions/assets/lab1/Bonus-2-StarDevSecOpsRepo.png differ diff --git a/submissions/assets/lab1/Bonus-3-FollowingProfessor.png b/submissions/assets/lab1/Bonus-3-FollowingProfessor.png new file mode 100644 index 000000000..f1436a6c5 Binary files /dev/null and b/submissions/assets/lab1/Bonus-3-FollowingProfessor.png differ diff --git a/submissions/assets/lab1/Bonus-4-FollowingNaghme98.png b/submissions/assets/lab1/Bonus-4-FollowingNaghme98.png new file mode 100644 index 000000000..155d47f9e Binary files /dev/null and b/submissions/assets/lab1/Bonus-4-FollowingNaghme98.png differ diff --git a/submissions/assets/lab1/Bonus-5-FollowingPierrePicaud.png b/submissions/assets/lab1/Bonus-5-FollowingPierrePicaud.png new file mode 100644 index 000000000..cca1ff3b7 Binary files /dev/null and b/submissions/assets/lab1/Bonus-5-FollowingPierrePicaud.png differ diff --git a/submissions/assets/lab1/Bonus-6-Followingdilfd2006.png b/submissions/assets/lab1/Bonus-6-Followingdilfd2006.png new file mode 100644 index 000000000..95cdd8356 Binary files /dev/null and b/submissions/assets/lab1/Bonus-6-Followingdilfd2006.png differ diff --git a/submissions/assets/lab1/Bonus-7-FollowingAn11y.png b/submissions/assets/lab1/Bonus-7-FollowingAn11y.png new file mode 100644 index 000000000..7351400da Binary files /dev/null and b/submissions/assets/lab1/Bonus-7-FollowingAn11y.png differ diff --git a/submissions/assets/lab1/Bonus-8-Followingdarik1201.png b/submissions/assets/lab1/Bonus-8-Followingdarik1201.png new file mode 100644 index 000000000..bc048b28f Binary files /dev/null and b/submissions/assets/lab1/Bonus-8-Followingdarik1201.png differ diff --git a/submissions/assets/lab1/Challenge1-Error.png b/submissions/assets/lab1/Challenge1-Error.png new file mode 100644 index 000000000..af02ba664 Binary files /dev/null and b/submissions/assets/lab1/Challenge1-Error.png differ diff --git a/submissions/assets/lab1/ChallengeSolved1.png b/submissions/assets/lab1/ChallengeSolved1.png new file mode 100644 index 000000000..715be11bf Binary files /dev/null and b/submissions/assets/lab1/ChallengeSolved1.png differ diff --git a/submissions/assets/lab1/Task1-1-Evidences.png b/submissions/assets/lab1/Task1-1-Evidences.png new file mode 100644 index 000000000..425eba2ae Binary files /dev/null and b/submissions/assets/lab1/Task1-1-Evidences.png differ diff --git a/submissions/assets/lab1/Task1-2-LocalStorage.png b/submissions/assets/lab1/Task1-2-LocalStorage.png new file mode 100644 index 000000000..17b7e376b Binary files /dev/null and b/submissions/assets/lab1/Task1-2-LocalStorage.png differ diff --git a/submissions/assets/lab1/Task1-3-Cookies.png b/submissions/assets/lab1/Task1-3-Cookies.png new file mode 100644 index 000000000..934c669c7 Binary files /dev/null and b/submissions/assets/lab1/Task1-3-Cookies.png differ diff --git a/submissions/assets/lab1/Task1-4-Headers.png b/submissions/assets/lab1/Task1-4-Headers.png new file mode 100644 index 000000000..48886709f Binary files /dev/null and b/submissions/assets/lab1/Task1-4-Headers.png differ diff --git a/submissions/assets/lab1/Task1-5-Assset.png b/submissions/assets/lab1/Task1-5-Assset.png new file mode 100644 index 000000000..b16282091 Binary files /dev/null and b/submissions/assets/lab1/Task1-5-Assset.png differ diff --git a/submissions/assets/lab1/Task1-6-RestartPolicy.png b/submissions/assets/lab1/Task1-6-RestartPolicy.png new file mode 100644 index 000000000..ce7f1b8d0 Binary files /dev/null and b/submissions/assets/lab1/Task1-6-RestartPolicy.png differ diff --git a/submissions/assets/lab1/Task2-Description.png b/submissions/assets/lab1/Task2-Description.png new file mode 100644 index 000000000..953d808e2 Binary files /dev/null and b/submissions/assets/lab1/Task2-Description.png differ diff --git a/submissions/lab1.md b/submissions/lab1.md new file mode 100644 index 000000000..96a01b62e --- /dev/null +++ b/submissions/lab1.md @@ -0,0 +1,239 @@ +# Lab 1 — Deploy OWASP Juice Shop and Set Up the Course Workflow + +## Task 1 — Deploy Juice Shop and write a triage report + +### Asset + +- **Image tag:** `bkimminich/juice-shop:v20.0.0` +- **Image digest:** `bkimminich/juice-shop@sha256:fd58bdc9745416afce8184ee0666278a436574633ea7880365153a63bfd418b0` +- **Host OS:** Ubuntu 25.10 +- **Docker version:** Docker version 29.1.3, build `29.1.3-0ubuntu3~25.10.1` + +The image digest identifies the exact immutable image content used in this run, +while the tag is the human-readable version reference. + +Task 1 asset host OS evidence + +### Deployment + +The container was started with: + +```bash +docker run -d --name juice-shop -p 127.0.0.1:3000:3000 bkimminich/juice-shop:v20.0.0 +``` + +The application was available at `http://127.0.0.1:3000`. The mapping +`127.0.0.1:3000->3000/tcp` binds the published port to the host loopback +interface, so other hosts cannot connect to this deliberately vulnerable +application directly through the network. The observed restart policy was +`no`, meaning Docker will not automatically restart the container after it +stops. + +Task 1 restart-policy evidence + +### Health + +The health checks produced the following observed results: + +- `docker ps`: `juice-shop Up About a minute 127.0.0.1:3000->3000/tcp` +- Request to `/`: `HTTP 200` +- Application version: `{"version":"20.0.0"}` +- Product count: `46` + +Together, these checks show that the container was running, the root route +responded successfully, the expected application version was deployed, and the +product data path returned the expected number of records. The product count +does not independently prove that a live database query occurred because the +response could also come from cached or preloaded data. + +The following capture records the container, health, version, product-count, +image-digest, and Docker-version checks. + +Task 1.1 terminal evidence for the Juice Shop deployment and health checks + +### Surface + +- **Login and registration:** The **Account** menu exposed the login page, which + contained email and password fields, password recovery, a **Remember me** + option, and a link for new customers to reach registration. +- **Products:** The product list loaded on the home page. Opening a product + generated requests for product data and reviews without requiring + authentication. +- **Admin or account area:** Selecting **Account** while unauthenticated + redirected to `/#/login`. No administrative area was visible to an + unauthenticated user. +- **Console errors:** Initially, no console errors were observed. Later, the + login page Console showed a request to `/rest/user/login` returning `401 + Unauthorized`. Directly opening that unexpected path produced an HTTP `500` + response with an Express stack trace, and Juice Shop then displayed a + notification that the **Error Handling** challenge had been solved. + +Juice Shop HTTP 500 response exposing the Express version, internal paths, source locations, and stack trace for the unexpected /rest/user/login path + +Juice Shop Error Handling challenge notification with the DevTools Console showing an HTTP 401 Unauthorized login request +- **Browser storage:** Before authentication, Local Storage was empty. The + application had two cookies: `language=en`, which stores the language + preference, and `cookieconsent_status=dismiss`, which records the cookie + banner choice. No authentication or session token was present. + +Juice Shop Local Storage view showing no stored keys before authentication + +Juice Shop cookies showing language and cookie consent preferences + + +### Headers + +Command: + +```bash +curl -sI http://127.0.0.1:3000 | head -20 +``` + +Observed output: + +```text +HTTP/1.1 200 OK +Access-Control-Allow-Origin: * +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +Feature-Policy: payment 'self' +X-Recruiting: /#/jobs +Accept-Ranges: bytes +Cache-Control: public, max-age=0 +Last-Modified: Mon, 28 Sep 2026 12:16:02 GMT +ETag: W/"26af-1a0e7f18f35" +Content-Type: text/html; charset=UTF-8 +Content-Length: 9903 +Vary: Accept-Encoding +Date: Mon, 28 Sep 2026 12:43:11 GMT +Connection: keep-alive +Keep-Alive: timeout=5 +``` + +- **`Content-Security-Policy`: missing.** Without a CSP, the browser has no application-defined allowlist restricting where scripts and other resources may load from. This removes a defense-in-depth control that can limit the impact of injected content, although the missing header alone does not prove that JavaScript injection is possible. +- **`Strict-Transport-Security`: missing.** The browser is not instructed to force future connections to use HTTPS. This does not prevent HTTPS from being used; it means HTTPS is not enforced through HSTS. The application was accessed over plain HTTP in this local lab. +- **`X-Content-Type-Options`: present as `nosniff`.** The browser is instructed to honor declared content types instead of MIME-sniffing a response as another executable type. +- **`X-Frame-Options`: present as `SAMEORIGIN`.** The page may be framed only by a page from the same origin, reducing exposure to cross-origin clickjacking. + +The two missing headers represent absent security hardening and are classified as **OWASP Top 10:2025 A02 — Security Misconfiguration**. + +HTTP response headers showing X-Content-Type-Options and X-Frame-Options present while CSP and HSTS are absent + +### Top 3 risks + +1. **Missing Content Security Policy — A02: Security Misconfiguration.** + The application did not provide a Content Security Policy, removing a + browser-enforced restriction on which scripts and resources may execute. If + another vulnerability allowed malicious content to be injected, the missing + CSP could increase its impact by leaving the browser with one fewer layer to + block the injected script; its absence alone does not prove that an XSS + vulnerability exists. + +2. **Unencrypted and unenforced transport — A04: Cryptographic Failures.** + The application was served over plain HTTP, and the response did not include + HSTS to enforce future HTTPS connections. If this configuration were exposed + over a network, an attacker able to intercept the traffic could read or + modify transmitted data, including credentials or session information; the + localhost-only binding limits this exposure in the current lab. + +3. **Inconsistent and revealing error handling — A10: Mishandling of + Exceptional Conditions.** An unexpected request returned HTTP `500` and + exposed the Express version, internal paths, source locations, and a stack + trace. These implementation details can help an attacker understand the + application and refine later attacks, while the successful Error Handling + challenge confirms that the failure was not handled gracefully or + consistently. + +## Task 2 — PR template + +The pull request template was created at `.github/PULL_REQUEST_TEMPLATE.md`. +It defines the required sections: + +- `Goal` +- `Changes` +- `Testing` +- `Artifacts & Screenshots` + +The template also includes the required checklist items: + +- `The title follows feat(labN): .` +- `No secrets or large temporary files are committed.` +- `submissions/labN.md exists.` + +Draft PR: + +The auto-fill behavior was validated by opening a PR form in the fork after the +template had been added to the fork's `main` branch. The description box loaded +the template sections and checklist before any manual PR description content was +entered. + +Task 2 pull request template auto-fill evidence + +## Task 3 — GitHub community + +Stars were added to the course repository and to the Simple Container API project, +and the professor, the TAs, and five classmates were followed. Stars help open +source maintainers because they signal interest and adoption, increase project +visibility, and help other people discover the project. Following team members +helps track their public activity, discover the repositories they work on, and +stay aware of shared project work. + +### Evidence + +**Star on the Simple Container API repository.** + +Star on the Simple Container API repository. + +**Star on the DevSecOps Intro course repository.** + +Star on the DevSecOps Intro course repository. + +**Course professor followed on GitHub.** + +Course professor followed on GitHub. + +**First classmate followed on GitHub.** + +First classmate followed on GitHub. + +**Second classmate followed on GitHub.** + +Second classmate followed on GitHub. + +**Third classmate followed on GitHub.** + +Third classmate followed on GitHub. + +**Fourth classmate followed on GitHub.** + +Fourth classmate followed on GitHub. + +**Fifth classmate followed on GitHub.** + +Fifth classmate followed on GitHub. + +## Bonus: CI smoke test + +The smoke test workflow was added at `.github/workflows/lab1-smoke.yml`. +It runs on `pull_request` events targeting `main`, uses workflow-level +`permissions: contents: read`, starts `bkimminich/juice-shop:v20.0.0` as a +service container, and polls `http://localhost:3000/rest/admin/application-version` +with `curl --silent --fail` for up to 60 seconds. + +Run URL: + +The successful job ran from `2026-09-29T06:43:37Z` to +`2026-09-29T06:43:56Z`, for a duration of approximately 19 seconds. The full +workflow run completed successfully at `2026-09-29T06:43:57Z`. + +Curl output excerpt: + +```text +Waiting for Juice Shop (1/12)... +{"version":"20.0.0"}Juice Shop is ready on attempt 2. +``` + +The same workflow was also attached to the official course draft PR, but GitHub +marked that run as `action_required` before creating any jobs because the course +repository requires approval before running Actions from forked pull requests. +