Computational Literacies Lab

0004.e Software vulnerabilities

On this page
  1. Vulnerability, threat, exploit, and risk
  2. The life of a flaw
  3. Flaws in code: trusting input
  4. Flaws in configuration and permissions
  5. Flaws inherited from suppliers
  6. Deciding what to fix first

Section 0004.d ended by noting that encryption usually fails through the software around it, and this section is about those weaknesses. It begins with four terms that are easy to blur, vulnerability, threat, exploit, and risk, because they organize everything after them. It then follows a flaw through time, from its discovery as a zero-day to the long period when a fix exists but hasn't been installed. Next come the most important kinds of flaw in code, which share a single root: a program trusts input it should have checked. Weaknesses don't only live in code, so the section then turns to devices and apps that are insecure as configured, and to the software a product inherits from its suppliers. It ends where 0004.c did, with deciding which flaws to fix first.

Vulnerability, threat, exploit, and risk#

A vulnerability is a weakness, such as a flaw in a program or a password left at its default. A threat is a potential source of harm, such as a criminal group or a careless employee. An exploit is a method, often a piece of code, that takes advantage of a vulnerability. In a news report about attackers using a tool against a flaw in a web server, the flaw is the vulnerability, the attackers are the threat, and the tool is the exploit. Risk combines the likelihood of harm with its impact, as in 0004.c. These distinctions matter in practice. A vulnerability exists apart from any attack, so a system's record of no incidents doesn't show that it has no flaws. And because likelihood and impact depend on where a flaw sits, the same flaw is a greater risk on a public server holding student records than on an isolated test machine, where few attackers can reach it and little is at stake. For the same reason, compensating controls, such as restricting which networks can reach a system, may lower an organization's actual risk by reducing likelihood, although the flaw itself remains until it is fixed.

The life of a flaw#

A zero-day vulnerability is a flaw that is exploited before a fix is available, so named because the vendor has had zero days to fix it. The label concerns timing; it says nothing about how severe the flaw is, how many systems it affects, or how easy it is to attack. Since there is no patch to apply, the response is to reduce exposure and monitor, for instance by limiting access to an affected portal to the school's internal network and reviewing its access logs daily, until a patch can be deployed. A new password, new hardware, or reinstalling the same software version leaves the flawed code in place. Some researchers sell newly found flaws to brokers instead of reporting them to the vendor, which leaves users exposed to whoever acquires the details, because a vendor can't fix a flaw it doesn't know about.

Once a patch exists, the flaw is no longer a zero-day, and the continuing exposure comes from not installing the fix. In fact, the danger often grows at that point, because a patch shows what was fixed, and attackers can study the changes to build attacks on systems that haven't yet been updated. This is why unpatched software is such a common route in. Many successful attacks use known flaws for which patches already exist, which makes regular updating one of the most effective defenses, and when a breach follows a known, patched flaw, the organization's update practice is the main failure. Patching depends on knowing what you have, so the first step for an organization that patches haphazardly is to build an inventory of its hardware and software. Publicly known flaws are tracked under CVE identifiers (Common Vulnerabilities and Exposures), standard names such as CVE-2021-44228 that let notices, vendors, and scanning tools all refer to the same flaw. Some systems can no longer be patched at all, such as an old computer whose vendor has stopped issuing updates. Their flaws will never be fixed, so they should be isolated from general networks while their replacement is planned.

Flaws in code: trusting input#

Most of the classic flaws in code share a root cause: a program takes input from outside, from a user, a web form, or a file, and handles it as if it were safe. The input may be too large for the space reserved for it, or it may contain text that the program ends up treating as instructions. The flaws below differ in where the input goes, but their defenses follow the same logic: check input against what is expected, keep data separate from commands, and do the checking somewhere an attacker can't skip.

A buffer overflow occurs when input exceeds the memory reserved for it and overwrites adjacent memory. That adjacent memory can include data that controls what the program does next, so a carefully crafted overflow can change a program's instructions. A program or form field that crashes when given extra-long input may be signaling exactly this kind of flaw, which may be exploitable. Bounds checking prevents it by rejecting or truncating input that is too large, and the check has to match the space actually reserved. A limit of 200 characters doesn't protect a 20-character space, since a 150-character value passes the check and still overflows. Loops that copy input must stop at whichever comes first, the end of the input or the end of the reserved space. To copy a name into an 8-character space:

for (int i = 0; i < name.len AND i < 8; i = i + 1)

With OR in place of AND, or <= in place of <, a long name still writes to index 8, one position past the end. Memory-safe languages, which check bounds automatically or prevent these errors by design, eliminate this class of flaw. Adding memory to the computer does nothing, since it doesn't change the size of the reserved buffer.

SQL injection places database commands in user input that a program adds directly to a query.1 Suppose a search page builds its query by inserting whatever the user types into SELECT * FROM students WHERE last_name = '...'. A user who types ' OR '1'='1 turns the condition into last_name = '' OR '1'='1', which is true for every row, so the query returns every student. That string is a classic test for the flaw. Typing % or * doesn't have this effect, since with = those characters are compared as ordinary text. The main defense is a parameterized query, which sends the typed text to the database as a parameter, apart from the command, so that it can only ever be data. Input validation, checking that input matches the expected form, and sanitizing, removing or neutralizing characters that have special meaning, add a second layer, while filtering out a single keyword leaves many other crafted inputs available. Validation must happen on the server, because checks in the browser can be bypassed: an attacker controls their own browser and can send requests directly to the server without passing through the page's check. Least privilege limits the damage if injection succeeds, since an injected command can do only what the application's database account is allowed to do, so an account that only reads one table can't delete the others. Detailed database error messages help attackers too, because table and column names help them craft working queries, so the server should keep those details in its logs and show users only a generic message.

Cross-site scripting (XSS) occurs when a site delivers untrusted input as code that runs in other users' browsers. A browser can't tell that a script delivered by a site originally came from another user, so it runs the script with the site's authority. In stored XSS, the script is saved on the site, for example in a product review, and reaches anyone who views the page. In reflected XSS, the script arrives in the request itself, usually in a crafted link, so it affects those who open the link, though the browser still treats it as coming from the site. A common goal is stealing session cookies, the values that identify a logged-in user to the site that issued them, since a stolen session cookie lets an attacker act on the site as that user without the user's password. XSS is prevented by validating input and escaping output, converting special characters when a page is generated so that browsers display user content as text instead of interpreting it as markup or scripts. Marking session cookies HttpOnly adds protection, because such cookies are sent with requests but aren't available to scripts running in the page.

Remote file inclusion occurs when a server executes an uploaded or linked file supplied by an attacker. If input lets a user choose which file the server loads and runs, the attacker can supply the code, and the server will run code the attacker controls. The best mitigation is an allow list (also called a white list) of approved content. A page that should load one of ten approved files can map each accepted value to one of those files and refuse everything else, so no input, however crafted, can select anything unknown. A block list (blacklist) of forbidden content is weaker, because it misses whatever it doesn't name: a list that blocks .php files still lets through .phtml, which some servers also run. Uploads need similar care. Store uploaded files in a location from which the server won't execute them, since a nightly scan for malicious files still leaves a window in which one can run. And examine each file's actual contents, handling it as data that never runs, because a file's name and extension can be faked.

Insecure deserialization is a less familiar flaw with the same root. Programs often save objects, such as a user's settings or shopping cart, by serializing them into a format such as JavaScript Object Notation (JSON) or Extensible Markup Language (XML), and deserialization reconstructs the objects from that data. If the data is under a user's control, for example stored in the user's browser, a user can alter it to trigger behavior the attacker chooses when the server rebuilds the objects, up to remote code execution, running the attacker's code on the server. Accepting a simple data format and validating its values against an expected structure limits what crafted data can do. Disguising the data doesn't help: Base64 is a reversible encoding, not protection, so anyone can decode a value, change it, and encode it again. Data stored on a client needs a cryptographic signature that the server checks on each request, which reveals any change; an encrypted connection protects the data on its way, but not from the client holding it. The risk is lower where untrusted input can't reach the deserializing code at all, though a claim that "only trusted data reaches it" should itself be checked.

Flaws in configuration and permissions#

Some weaknesses come not from faulty code but from how a product is set up. Many devices, particularly Internet of Things (IoT) devices such as cameras and smart thermostats, are shipped with default credentials, usernames and passwords published in their manuals and online, so anyone can find them and take a device over unless they are changed. Large botnets such as Mirai were built this way, from many devices that stayed in service with logins their makers had published. Owners can assign each device its own credentials and restrict which networks can reach it. Makers can do better still by shipping each unit with a unique password or forcing a change at setup, so that no published login works. A related danger is a hidden factory account, a login built in by the maker and not shown to users. It works as a backdoor, independent of the password staff changed, so anyone who learns it can get in, and users may be unable to remove it.

Phones and tablets raise the same question at the level of individual apps, through the permissions they request. An app that asks for permissions unrelated to its function, such as a flashlight app asking for contacts and location, poses a privacy and security risk, and disclosing the sharing in a privacy policy doesn't remove it. Approval by an app store doesn't ensure that an app's permissions fit its purpose. The test is to judge each permission against what the app actually does: a language app's request for the microphone is reasonable if a feature, such as pronunciation practice, depends on it. Granting only the access a function requires, for example allowing the microphone only while the app is in use, limits an app's reach, and when two apps offer the same function, the one with less access creates less risk for students.

Flaws inherited from suppliers#

The last kind of weakness enters through software a product didn't write. A supply chain attack compromises a trusted vendor, update, or library, so that malicious code reaches all of its downstream users at once. Because the attack targets the trusted source, the usual signs of trust can cover it: if malicious code is added before an update is signed, the valid signature covers the malicious code too. In typosquatting, an attacker publishes a malicious package under a name close to a popular one, so that a developer who mistypes it installs the attacker's code. Encryption doesn't help here, since it protects the download even of the wrong package. The defenses are verifying package names and sources and installing from a list of approved versions. Indirect dependencies, the libraries that a product's libraries depend on, are part of the product too, and each one is another route by which flawed or malicious code can arrive. A software bill of materials (SBOM), a list of every component a product uses, lets an organization quickly find which of its products are affected when a flaw is announced in a component. Suppliers can also be routes in a broader sense: a smaller firm may be less well defended than the bank it serves, yet its access to the bank is trusted, so attackers can use it as a route into the better-defended customer.

Deciding what to fix first#

No organization can fix every flaw at once, so the reasoning that began this section comes back at its end. Limited effort should go where it reduces the most risk, which means ranking flaws by their severity, the exposure of the systems where they exist, and evidence that they are being actively attacked. By this reasoning, a moderate flaw on an Internet-facing server that attackers are already exploiting can deserve attention before a more severe one on an isolated machine. However careful the prioritization, some attacks will succeed, and 0004.f turns to preventing breaches by design and responding when they happen.

  • vulnerability: a weakness
  • threat: a potential source of harm
  • exploit: a method that takes advantage of a vulnerability
  • compensating control: a safeguard that reduces the risk of a flaw that hasn't been fixed
  • zero-day: a flaw exploited before a fix is available
  • CVE identifier: a standard name for a publicly known flaw
  • buffer overflow: input that exceeds its reserved memory and overwrites adjacent data
  • bounds checking: rejecting input that is too large for its reserved space
  • memory-safe language: a language that prevents out-of-bounds memory access by design
  • SQL injection: database commands placed in user input that is added directly to a query
  • parameterized query: a query that sends input to the database separately from the command
  • cross-site scripting (XSS): a site delivering untrusted input as code that runs in other users' browsers
  • stored XSS: a script saved on the site
  • reflected XSS: a script carried in the request, usually a link
  • escaping output: converting special characters so that browsers display content as text
  • HttpOnly: a cookie setting that keeps page scripts from reading the cookie
  • remote file inclusion: a server executing an uploaded or linked file from an attacker
  • sanitizing input: removing or neutralizing characters in input that have special meaning; input that skips this step is called unsanitized
  • allow list (white list): a list of approved items, refusing everything else
  • block list (blacklist): a list of forbidden items, allowing everything else
  • deserialization: reconstructing objects from formats such as JSON or XML
  • remote code execution: an attacker running code on another system
  • default credentials: published usernames and passwords that ship with a device
  • backdoor: a hidden way into a system that bypasses normal authentication
  • supply chain attack: compromising a trusted vendor, update, or library to reach its users
  • typosquatting: publishing a malicious package under a name close to a popular one
  • software bill of materials (SBOM): a list of the components a product uses

Practice: practice questions for 0004.e

  1. MWC2, Riddle Server: views, models, fields, subclasses, and an API specification. ↩