CWE-477: Use of Obsolete Function
Overview
Obsolete functions remain callable long after they've been superseded, because implementations keep shipping them for compatibility with existing code. They're deprecated for concrete reasons - missing bounds checking, predictable output, a race condition in the API's design, or a verification step the function had no way to turn on - and each one has a maintained replacement that closes the specific gap.
Relationship to Other CWEs
Three CWEs cover "this function is a problem", and they answer different questions.
- CWE-477 (this page) - covers the function's status: whether the standard, the platform, or the library that publishes it has withdrawn, deprecated, or superseded it.
- CWE-676 (Use of Potentially Dangerous Function) - covers functions that are current, supported API with a documented safe calling convention, where the risk is a caller skipping the check the function itself doesn't enforce.
strcpy(),sprintf()andsystem()are all still standard C - a scanner reporting them under CWE-477 has picked the wrong number, and the guidance a developer needs for them is on the CWE-676 page. - CWE-242 (Use of Inherently Dangerous Function) - covers functions with no safe calling convention at all, no matter how carefully they're used.
The three overlap where a function is more than one of them, and gets() is the case that lands on two of them: it cannot be called safely (CWE-242), and it was removed from the C11 standard outright (CWE-477). Where the categories differ is what the fix has to be. An obsolete function has a named successor and the work is a substitution; a merely dangerous one is often correct where it stands, and the work is a judgement call about whether this call site upholds the obligation.
OWASP Classification
A03:2025 - Software Supply Chain Failures
Risk
Medium-High: Withdrawn functions cause buffer overflows (unbounded copies such as gets), missing peer verification (a TLS helper that authenticated nothing), password hashes that vary with the host libc, and race conditions (predictable temp file names). The severity is usually inherited from whatever the function was doing rather than from its obsolescence: a withdrawn helper that was the security control is high, and one that has simply been renamed is a maintenance item.
Remediation Steps
Core Principle: Treat use of a known-obsolete function as a finding regardless of whether it's exploitable today; replace it with the maintained equivalent, since the obsolete function offers no security guarantee to fall back on.
Trace the Data Path
- Source: Every call site using a function on the language's obsolete/deprecated list
- Sink: Whatever the function's output feeds - a buffer, a token, a cryptographic digest, a file path
- Missing control: No check (manual or automated) flagging the function as unsafe before it reached production
Replace With the Maintained Equivalent (Primary Defense)
// SECURE - pseudo-code
// bounded operations instead of unbounded ones
copy_bounded(dest, source, dest_capacity)
// the successor helper, whose defaults are the checks the old one skipped
connection = verified_tls_connect(host, port)
// atomic, unpredictable resource creation instead of predict-then-create
handle = create_unique_resource_atomically()
Each obsolete function maps to a specific, current replacement - see the table below and the language-specific pages for exact names.
Categorize by Risk Before Prioritizing
- String/buffer functions: buffer overflow risk - highest priority if the input can be attacker-influenced
- Withdrawn security helpers: a deprecated TLS wrapper, password-hashing module or credential API - high priority, because the removed function usually verified less than its replacement does, and there is no argument that makes it correct. Weak algorithms reached through a current API are CWE-327/CWE-328, not this
- Renamed or relocated helpers: the successor is behaviourally identical - low priority as a weakness, but it still breaks the build on the release that removes it, so it is scheduling work rather than security work
- Resource creation functions: race conditions (TOCTOU) - priority depends on whether the resource path is guessable and shared with untrusted processes
Find Every Occurrence
A single obsolete function is rarely used once. Search the whole codebase for the same function name and fix every call site in one pass.
Test the Fix
- Test string/buffer replacements at boundary conditions: empty input, exact buffer size, oversized input
- Where the withdrawn function produced stored data - a password hash from a removed credential module, say - test that existing values still verify through the migration window, and that new ones are written in the successor's format
- Run the build and test suite against the newest runtime the code must support, with deprecation warnings promoted to errors, so the next removal surfaces a release early rather than as an outage
- Re-scan with the security scanner to confirm no instances of the obsolete function remain
Common Vulnerable Patterns
An unbounded copy kept because it still compiles
// VULNERABLE - a withdrawn read-a-line function with no destination capacity
// (the C original is gets(), removed from the standard in C11)
read_line_unbounded(dest) // no length argument - overflow on any input longer than dest
Why this is vulnerable: the function was withdrawn because its signature cannot express the one fact the caller needs to supply - how much room the destination has. Nothing about the call site records the obligation, so it holds only as long as nobody changes the declaration of dest, and the compiler will not notice when someone does. That is also why the standard removed it rather than documenting a safe way to call it: there isn't one, which is what makes this call gets-shaped and not strcpy-shaped.
Swapping in the bounded replacement is the fix and is not automatically sufficient. Bounded copies differ in what they do when the source does not fit: some truncate and terminate, some truncate and leave the destination unterminated, and some return a length the caller is expected to check. A mechanical substitution that ignores the difference trades an overflow for an unterminated string, which is CWE-170 and reads as fixed to both a scanner and a reviewer.
A withdrawn helper that was the security control
// VULNERABLE - a superseded TLS helper that verifies nothing by default
connection = legacy_tls_wrap(socket) // no chain check, no hostname check
send(connection, credentials)
Why this is vulnerable: the call still does most of what its name promises. The traffic is genuinely encrypted, a packet capture shows ciphertext, and every functional test passes - so nothing in normal operation indicates that the one thing the helper does not do is establish who is on the other end. Anyone able to answer for the address can present any certificate and read and rewrite what follows.
This is the shape that makes obsolescence worth treating as a finding on its own. The successor exists because the original could not be fixed in place: no argument added at this call site turns verification on, because the parameter was never there. The fix is a different call, not a safer configuration of the same one.
Obsolescence has a second consequence worth planning for. A withdrawn function does not stay a warning; on the release that removes it the call fails outright, so the finding turns into an outage whenever the runtime is upgraded. Schedule the replacement rather than deferring it, and establish first which interpreter or standard version the code targets.
Secure Patterns
// SECURE - bounded copy with an explicit capacity
copy_bounded(dest, source, dest_capacity)
// SECURE - the maintained successor, which verifies by default
connection = verified_tls_connect(host, port)
Why this works: Each replacement takes as a required argument, or applies by default, the thing the withdrawn function had no way to express - a destination capacity the caller must supply, or the chain and hostname checks the old TLS helper skipped. That is what makes these substitutions rather than judgement calls: there is no configuration of the original that reaches the same place, which is why the publisher replaced it instead of documenting it.
The substitution is still not automatic, because successors differ in what they do at the boundary the original ignored. A bounded copy may truncate and terminate, truncate and leave the destination unterminated, or return a length the caller must check; a verifying TLS connect will now reject peers the old one accepted. Read the successor's contract rather than assuming it is the old function with the hole closed.
Common Obsolete Functions and Replacements
Every row below is a function the publisher has withdrawn, deprecated or superseded. Weak algorithms are a different CWE and are not listed here - see the note after the table.
| Category | Obsolete | Replacement |
|---|---|---|
| C strings | gets (removed in C11) |
fgets |
| Temp files | tmpnam, tempnam |
mkstemp |
| Misc C | getwd(), bcopy(), bzero() (all removed from POSIX.1-2008) |
getcwd(), memmove(), memset() |
| Python | ssl.wrap_socket() (removed 3.12), crypt, telnetlib, pipes (removed 3.13) |
ssl.SSLContext.wrap_socket(), argon2/bcrypt, an SSH client, shlex.quote() |
Four things about that table are easy to get wrong when applying it:
- MD5, SHA-1, DES, RC4 and 3DES are not on it. They are algorithms, not functions, and choosing a broken one is CWE-327 - or CWE-328 specifically for hashes - which is where the replacements and the migration guidance live.
hashlib.md5()in Python andMessageDigest.getInstance("MD5")in Java are current, supported, undeprecated API that a scanner will nonetheless report as CWE-477, and the fix is a judgement about what the digest protects rather than a substitution. Where a binding to one of these algorithms has genuinely been withdrawn - OpenSSL directs callers from the low-levelMD5()/SHA1()functions to the EVP interface, for instance - that binding is a CWE-477 finding in its own right, separately from whatever the algorithm question decides. rand()andsrand()are not on it either, for the same reason. Both are current standard C; no standard has deprecated or removed them, and they remain correct for the many uses that do not need unpredictability. Using one to generate a token or a session identifier is a weak-PRNG finding - CWE-338, with CWE-330 for the broader question - and it turns on what predicting the value would buy an attacker, which is a judgement no function-status rule can make. The C page carries the concrete replacements because scanners do reportrand()here, but the number is wrong.bcopy()maps tomemmove(), notmemcpy().bcopyis specified to behave correctly when the source and destination overlap;memcpyis explicitly undefined in that case. Substitutingmemcpyis the usual reflex - the argument order even matches once you swap it - and it compiles, passes tests on non-overlapping calls, and corrupts data on the overlapping ones.strcpy,strcatandsprintfare not on this list. They are current standard C with a documented safe calling convention, which puts them under CWE-676 rather than here; that page has the bounded replacements and the reasons to prefersnprintfover thestrn*family.getsis the one that genuinely belongs to both, having been removed from the standard outright.
Language-Specific Guidance
- C -
getsand the POSIX removals (getwd,bcopy,bzero),tmpnam()/tempnam(), and their bounded and atomic replacements; plusrand()/srand(), which scanners file here and which the page hands to CWE-338 - Python -
ssl.wrap_socket()(removed 3.12), thecryptmodule and the PEP 594 removals (3.13), and their named successors