CS375: Software Engineering - Secure Software Engineering

Activity Goals

The goals of this activity are:
  1. To define the CIA triad (confidentiality, integrity, and availability) and use it to describe what a given attack violates
  2. To explain why security is a lifecycle concern woven through requirements, design, coding, testing, and deployment, rather than a feature bolted on at the end
  3. To recognize common OWASP Top 10 vulnerability categories (injection, broken access control, cryptographic failures, and vulnerable dependencies) in real code
  4. {"To apply core secure-coding defenses"=>"input validation, parameterized queries, output encoding, least privilege, and keeping secrets out of source control"}
  5. To perform a lightweight STRIDE threat model of a small web service and propose a mitigation for each identified threat
  6. To read a CVE/CWE identifier and a dependency advisory, and to explain how automated scanning (dependency review, static analysis) can be integrated into a CI pipeline

Supplemental Reading

Feel free to visit these resources for supplemental background reading material.

The Activity

Directions

Consider the activity models and answer the questions provided. First reflect on these questions on your own briefly, before discussing and comparing your thoughts with your group. Appoint one member of your group to discuss your findings with the class, and the rest of the group should help that member prepare their response. Answer each question individually from the activity, and compare with your group to prepare for our whole-class discussion. After class, think about the questions in the reflective prompt and respond to those individually in your notebook. Report out on areas of disagreement or items for which you and your group identified alternative approaches. Write down and report out questions you encountered along the way for group discussion.

Model 1: The CIA Triad: Thinking Like an Attacker

PropertyPlain-English questionExample of a violation
ConfidentialityCan only the right people read this?Another user reads your private notes; a password database is leaked.
IntegrityCan only the right people change this, and can we detect tampering?A student edits a grade in the database; a JWT payload is forged.
AvailabilityIs the system up and usable when needed?A flood of requests takes the service offline during finals.

Questions

  1. For each row, name a feature in a system you have built (in this class or elsewhere) that depends on that property. What would go wrong for a real user if it failed?
  2. A developer says, "our app has no login, so it has no security requirements." Using the three properties above, give a concrete counterexample -- something an attacker could still violate.
  3. Security is often framed as reducing the attacker's options rather than achieving perfect safety. Why is "100% secure" an unrealistic goal, and how is this similar to the argument in the testing activity that you cannot test every possible input?
  4. Recall the Therac-25 case study. Which leg(s) of the CIA triad did its failures most violate, and how does that connect security to safety and professional ethics?

Model 2: Injection: The Little Bobby Tables Problem (OWASP A03)

The classic (broken) way to build a query by pasting user input directly into a SQL string:

// UNSAFE: username comes straight from a web form
const q = "SELECT * FROM Users WHERE name = '" + username + "'";
db.query(q);

If a user types name as '; DROP TABLE Students;--, the string the database actually runs is no longer the query you intended.

XKCD Exploits of a Mom (Little Bobby Tables)

The fix is a parameterized query (also called a prepared statement), where user input is sent as data, never as executable code:

// SAFE: the ? is a placeholder; the driver keeps username as data
db.query("SELECT * FROM Users WHERE name = ?", [username]);

Questions

  1. In your own words, what is the root cause of an injection vulnerability? What did the unsafe code confuse that the safe code keeps separate?
  2. You saw this exact XKCD comic and the node.js fix in the databases activity. Injection is not only SQL: name one other place user input becomes executable (hint: rendering a note that contains <script> in the view-testing example). What is the analogous defense there?
  3. "Validate input" and "parameterize the query" are two different defenses. Which one actually stops the attack above, and why is input validation alone (for example, rejecting apostrophes) a fragile way to prevent injection?
  4. This category is OWASP A03: Injection and maps to CWE-89 (SQL Injection). Why is it useful for a team to refer to a shared catalog like OWASP or CWE instead of everyone inventing their own names for bugs?

Model 3: Broken Authentication and Access Control (OWASP A01/A07)

A JSON Web Token (JWT) has three parts. The payload is readable by anyone who has the token -- it is only Base64-encoded, not encrypted:

header.payload.signature
// payload (decoded): {"sub": 1, "username": "alice", "exp": 1699999999}

The server's requireAuth middleware calls jwt.verify(token, SECRET), which recomputes the signature with a secret only the server knows. A forged or edited token fails verification and the request is rejected with 401 before any controller runs.

Questions

  1. You traced this exact handshake in the MVC activity. If the payload is readable and editable by anyone, what stops a user from changing "sub": 1 to "sub": 2 and impersonating another user?
  2. Authentication (who are you?) and authorization (what are you allowed to do?) are different. In the "delete only your own notes" exercise from the MVC activity, a valid logged-in user could still try to delete someone else's note. Which check prevents this, and which layer should enforce it?
  3. Broken access control is OWASP A01 -- the most common category in the 2021 Top 10. Why do you think "forgot to check whether this user owns this resource" is so easy to get wrong, especially as a team adds new routes over a semester?
  4. Why is it a good security property that an unauthenticated request is rejected at the middleware, so the controller and model layers never even run? Connect this to the idea of least privilege and to failing closed.

Model 4: Secrets, Hashing, and Signatures (OWASP A02: Cryptographic Failures)

From the git assignment: you generate a key pair. Data encrypted with your public key can only be decrypted with your private key, and a digital signature made with your private key can be verified by anyone holding your public key -- proving you produced it (integrity + authenticity).

Two ideas people often confuse:

  • Hashing (e.g. storing passwords): a one-way function. You never "un-hash." You store hash(password + salt) and re-hash on login to compare.
  • Encryption: two-way. You encrypt so an authorized party can later decrypt.

Questions

  1. "Treat your private key like a password" (from the git assignment). Why should a password be hashed (one-way) in your database, but a session's data sometimes encrypted (two-way)? Which one is appropriate for storing user passwords, and why is plain hash(password) without a salt still weak?
  2. The JWT_SECRET from the MVC example, a database password, and an API key are all secrets. What goes wrong if one is committed to a public git repository? (This is CWE-798, Hardcoded Credentials.) How do .env files, .gitignore, and GitHub Actions secrets.* help?
  3. Encoding, hashing, and encryption are three different things people call "encryption." The JWT payload is only encoded (Base64). Why does that mean you must never put a password or secret in a JWT payload?

Model 5: STRIDE Threat Modeling

STRIDE is a checklist for finding threats. Walk your design and, for each part, ask whether each threat applies:

LetterThreatViolatesExample on the note-taking service
SSpoofing (pretending to be someone)AuthenticityForging a JWT to act as another user
TTampering (unauthorized change)IntegrityEditing another user's note via a missing ownership check
RRepudiation ("I didn't do that")Non-repudiationNo audit log of who deleted a note
IInformation disclosureConfidentialityAn error message leaks a stack trace or another user's data
DDenial of serviceAvailabilityA giant note payload exhausts memory
EElevation of privilegeAuthorizationA normal user reaches an admin-only route

Questions

  1. Pick one functional requirement from your team project. Walk the six STRIDE letters against it and write down at least three plausible threats. You do not need three for every letter -- some will not apply, and saying why is part of the exercise.
  2. For each threat you found, propose one mitigation. Notice how many map back to defenses you already know: parameterized queries, an ownership check, input size limits, hashing, a log entry. Threat modeling mostly organizes defenses you have already met.
  3. Threat modeling is cheapest during design -- the same lesson as the cost-of-change curve you saw with requirements. Give one reason a threat found on a whiteboard during design is far cheaper to fix than the same threat found by an attacker in production.

Security Is Not a Feature You Add at the End

It is tempting to picture security as a lock you install on the door once the house is built: finish the app, then “make it secure.” Real software does not work that way. A missing ownership check is a design decision; a password stored in plaintext is a coding decision; a dependency with a known vulnerability is a deployment decision. Each of these lives in a different phase of the lifecycle you have studied all semester, which is exactly why professional frameworks like the NIST Secure Software Development Framework (SSDF) describe security practices for every phase, from requirements through response.

This module is a tour, not an encyclopedia. The goal is to give you a shared vocabulary (the CIA triad, the OWASP Top 10, STRIDE) and to show you that most of the concrete defenses are things this course has already taught you in other contexts. You have written parameterized queries, verified JWTs, generated key pairs, and escaped HTML in a view test. Here we name those defenses, connect them to the vulnerabilities they stop, and organize them into a repeatable practice.

The OWASP Top 10, Mapped to Defenses You Already Know

The OWASP Top 10 is the industry’s shared, evidence-based list of the most critical web application risks. You do not need to memorize it, but you should be able to recognize the big categories and, more importantly, name the defense for each. Several of these you have met already in CS375:

OWASP category (2021) What it is A defense you already practiced
A01: Broken Access Control A logged-in user does something they should not be allowed to do The “delete only your own notes” ownership check from the MVC activity
A02: Cryptographic Failures Sensitive data exposed through weak or missing crypto Public/private keys and digital signatures from the git assignment; hashing passwords with a salt
A03: Injection Untrusted input is executed as code (SQL, HTML/XSS, shell) Parameterized queries from the databases activity; escaping <script> in the view test
A05: Security Misconfiguration Insecure defaults, verbose errors, secrets in the repo Keeping JWT_SECRET in .env / GitHub Actions secrets.*, not in source
A06: Vulnerable and Outdated Components Using a dependency with a known CVE Dependency review in CI; see the supply-chain note below
A07: Identification and Authentication Failures Weak login, forgeable sessions jwt.verify rejecting forged tokens in the MVC activity

The lesson of this table is the through-line of the whole module: you are not starting security from zero. You are organizing and naming defenses you have already used, and learning to reach for them deliberately instead of by accident.

Supply-Chain Security Is Real, and It Happened Here

Category A06 (Vulnerable and Outdated Components) can feel abstract until it happens to a project you depend on. It happened to this course website. If you look at this repository’s git history, you will find a commit titled “replace compromised polyfill.io with Cloudflare cdnjs mirror.” In 2024, the widely used polyfill.io script service was taken over and began serving malicious code to every site that embedded it. The fix was to stop trusting the compromised source and point at a reputable mirror instead.

That is a supply-chain attack: you wrote no vulnerable code, but you included someone else’s, and their compromise became yours. This is why modern CI pipelines run dependency review (for example, GitHub Dependabot and npm audit), which check your dependencies against the National Vulnerability Database on every push – exactly the kind of automated gate you built in the CI/CD activity.

Reading a Vulnerability: CVE, CWE, and CVSS

Three acronyms show up constantly, and they answer three different questions:

  • A CWE (Common Weakness Enumeration) names a class of bug. “SQL injection” is CWE-89; “hardcoded credentials” is CWE-798. It is the shared vocabulary a team uses so that “we have an injection bug” means the same thing to everyone.
  • A CVE (Common Vulnerabilities and Exposures) names a specific vulnerability in a specific product, e.g. “CVE-2021-44228” (Log4Shell). CVEs live in the NVD.
  • A CVSS (Common Vulnerability Scoring System) score, from 0.0 to 10.0, rates how bad a given CVE is, so a team can triage what to fix first. It is maintained by FIRST.

When Dependabot opens a pull request that says “bump library X: fixes CVE-2023-1234 (CWE-79, CVSS 7.5),” you now have the vocabulary to read it: a specific vulnerability, of a known weakness class, rated high severity.

From Bugs to Threats: Why Model at All?

Finding bugs one at a time is reactive. Threat modeling is the proactive counterpart: before (or while) you build, you walk your design and ask “how could this be abused?” The STRIDE model above turns that open-ended question into a checklist – Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege – run against each part of your system.

Threat modeling is cheapest at the whiteboard, for the same reason a requirements bug is cheaper to fix than a production bug: the further downstream a problem is found, the more expensive it is to fix. A threat you catch during design costs a sentence in a document. The same threat caught by an attacker in production can cost user trust, data, and – as the Therac-25 case reminds us – in safety-critical systems, far more than that.

Your Turn: Threat Model Your Project

Bring your team’s project to this exercise. These tie directly into your Test Plan and Design deliverables, and set up the Security assignment and (for those specializing) the Security Capstone.

  1. Pick one functional requirement of your project. Run all six STRIDE letters against it and write down every plausible threat. For any letter that does not apply, say why – ruling threats out with a reason is part of the method.
  2. Propose one mitigation per threat. Label each with the OWASP category and/or CWE it addresses, and note which defense from this course it corresponds to (parameterized query, ownership check, input size limit, hashing, audit log, etc.).
  3. Find one dependency risk. Run npm audit (or your platform’s equivalent) on your project, or enable Dependabot, and identify one flagged dependency. What CVE/CWE is it, and what is its severity?
  4. Write one abuse-case test. In the testing activity you wrote tests for what should happen. Write one test for what should not be allowed – for example, “a request without a valid token receives 401,” or “user Bob cannot read user Alice’s note.” This is a security regression test, and it belongs in your project’s test plan.
  5. Reflect (connect to ethics). Re-read one question from the Therac-25 case study. How does treating security as a lifecycle concern – rather than a final checkbox – also serve the professional and ethical obligations of a software engineer?

Submission

I encourage you to submit your answers to the questions (and ask your own questions!) using the Class Activity Questions discussion board. You may also respond to questions or comments made by others, or ask follow-up questions there. Answer any reflective prompt questions in the Reflective Journal section of your OneNote Classroom personal section. You can find the link to the class notebook on the syllabus.