Instructor: In this lesson, we will learn
about memory-related vulnerabilities.
Memory-related vulnerabilities refer to flaws
in how an application handles memory,
potentially allowing attackers to execute malicious code,
cause crashes, or leak sensitive information.
Memory-related vulnerabilities include deprecated functions,
unsafe memory utilization, overflows, race conditions,
and time of check to time of use vulnerabilities.
Deprecated functions are older, insecure functions
that are still in use.
The use of deprecated functions can lead
to unsafe memory utilization and expose a system
to vulnerabilities like buffer overflows.
In a buffer overflow attack, an attacker overflows
a memory buffer to overwrite adjacent memory,
and over overflow attack can lead to the execution
of arbitrary code, allowing the attacker
to take control of the system, corrupt data
or crash the application entirely.
Overflows and unsafe memory usage
often create opportunities for race conditions.
A race condition occurs when the timing
of operations is manipulated to exploit the system.
Specifically, a race condition can occur
when two processes attempt
to modify the same data concurrently leading
to unpredictable results, such
as a financial transaction being processed twice.
A specific type of race condition is a time of check to time
of use condition.
To exploit a time of check to time of use vulnerability,
an attacker may change the system's state
between the time the system checks a condition
and the time it uses the result leading
to unexpected behavior.
Let's learn more about deprecated functions,
unsafe memory utilization, overflows, race conditions,
and time of check to time of use vulnerabilities.
First, we have deprecated functions.
Deprecated functions are functions or commands
in programming languages or libraries
that are no longer recommended for use
and often lack the security features
of their modern counterparts.
These functions may still work within the application,
but are outdated, meaning they don't
meet current security standards.
Developers keep deprecated functions around
for compatibility reasons,
but over time, these functions should be removed
from programming libraries altogether.
In software security, deprecated functions become a problem
because they often lack protection mechanisms such
as input, validation, or memory safety, which can lead
to injection attacks.
For example,
in the C programming language, functions like gets ()
for reading strings do not have built-in buffer size
checks potentially leading to buffer overflow attacks.
In contrast, modern alternatives
like fgets () allows developers
to specify buffer size, reducing the overflow risk.
Second, we have unsafe memory utilization.
Unsafe memory utilization occurs when an application fails
to manage memory correctly leading to vulnerabilities such
as buffer overflows, and memory leaks.
A memory leak is a condition
in which a program continuously allocates memory
without releasing it, eventually exhausting all
available memory and causing the application
or system to slow down or crash.
One of the biggest dangers of unsafe memory utilization is
that it can grant attackers access to critical areas
in memory, allowing them
to execute arbitrary code, escalate privileges
or access sensitive information, usually
through overflow attacks, which will be discussed next.
To protect against unsafe memory practices, developers
should implement strict memory checks, validate user input,
and use programming languages
that manage memory more securely.
Additionally, tools
like Address Space Layout Randomization
and Data Execution Protection can help reduce
the effectiveness of memory based attacks.
Address Space Layout Randomization randomizes
the memory addresses used by the system
and application processes, making it harder for attackers
to predict where to inject malicious code.
Next, Data Execution Protection prevents executable code
from running in certain areas of memory
that should only store data blocking attacks
that attempt to execute harmful instructions
from these protected regions.
Together, Address Space Layout Randomization,
and Data Execution Protection add layers
of protection against memory exploits
by disrupting common attack patterns
and restricting executable permissions.
Third, we have overflows.
Let's discuss overflow vulnerabilities
in two categories, buffer overflows and integer overflows.
A buffer overflow occurs when a program tries
to store data outside the designated memory range
or "buffer" allocated by the code.
Think of a buffer like a cup designated
to hold only 16 ounces of liquid.
If you pour in 20 ounces, the cup overflows spilling
onto the table.
Here, the cup represents the memory buffer
and any excess data spills out
of the buffer potentially causing disruptions
or even security breaches.
For instance, storing a 10 digit phone number
in a buffer meant only for eight digits would cause
the last two digits to overflow into adjacent memory,
corrupting the data that is stored there.
Attackers exploit this overflow effect to manipulate memory
and potentially execute malicious commands.
To fully understand this, we need
to see how a program manages memory.
When a program launches, it reserves an area
of memory called the stack, which stores essential data
like the return address for function calls.
If an attacker overfills this stack,
they can overwrite the return address
with one pointing to their malicious code.
This manipulation is called stack smashing
and often involves a technique called a NOP
or NOP Slide where attackers fill the stacks with NOPs
where NOP stands for no operation
and is interpreted as do nothing.
The NOP instructions create a path
or a slide that directs program control
to the attacker's code.
An integer overflow on the other hand,
occurs when a mathematical result exceeds
the storage capacity of its variable.
Unlike buffer overflows, integer overflows happen
within a variables designated range.
For example, a storage space
with only two digits caps out at the number 99,
so adding 90 and 17 would produce an answer
of 107, which is too large for the variable.
This overflow typically causes the value
to wrap around, changing,
for instance, a positive number into a negative number.
Attackers can use integer overflows
to alter buffer boundaries, potentially leading
to buffer overflow attacks.
Preventing these vulnerabilities requires boundary checks
and secure coding practices,
including choosing suitable variable sizes
and validating all data inputs.
Fourth, we have race conditions.
A race condition is a software vulnerability
that occurs when the outcome
of executing processes depends on the timing
and order of certain events,
but those events do not execute as the developer intended.
Essentially, this means the system is racing
to complete multiple processes
at the same time leading to unpredictable results
if an attacker interferes with the timing
of these operations.
For instance, if a legitimate user action
and an attacker's action are processed
at the same time, the attacker
may get their request executed first,
exploiting the race condition to achieve their goal.
Race conditions commonly occur when multiple threads attempt
to access or modify shared resources simultaneously
without proper synchronization.
A well-known example
of a race condition exploit is the Dirty COW vulnerability,
a privilege escalation attack on Linux
and Android systems discovered in 2016.
Dirty COW exploited a flaw in the Linux copy
on write mechanism, a memory management function,
allowing attackers to escalate their privileges
by turning read only access to files
into write access, despite system protections.
This exploit was particularly dangerous as it left no traces
in system logs making it difficult to detect.
So race conditions like Dirty COW can affect various
resources including memory management systems, databases,
and file systems.
To defend against race conditions,
developers can implement proper synchronization techniques
such as locking mechanisms to control access
to shared resources, and ensure events occur
in the intended sequence.
Fifth and last, we have a time of check
to time of use vulnerability.
This specific type
of race condition occurs when there is a delay
between an applications, a verification of a resource,
which is the check, and its subsequent use of that resource,
which is the use.
During this gap, if the resource or data is modified,
it may no longer meet the original requirements
or security checks established at the time of verification.
This can allow attackers to exploit the delay
to manipulate data, bypass security checks,
or gain unauthorized access.
For example, consider a system where
a user requests permission to edit a file.
The application checks if the user has the right permissions
to access that file.
This is the check, and then allows them to open
or modify the file, this is the use.
In a time of check to time of use attack, an attacker
with lower privileges could time their request
to replace the target file with another sensitive file
after the permission check,
but before the system grants access.
As a result, they may gain unauthorized access
to files they otherwise wouldn't be able to modify.
To mitigate time of check to time
of use vulnerabilities, developers can use locks
and mutexes to control access
to resources during processing.
A mutex, which is short for Mutual Exclusion,
is a locking mechanism
that ensures only one thread can access
a particular code section at any time, reducing the risk
of concurrent execution errors that could lead
to a race condition.
Additionally, many database systems offer
resource locking mechanisms to prevent simultaneous actions.
For example, SharePoint locks files when a user checks them
out for editing, allowing other users to view
but not edit the file.
This maintains version control
and prevents conflicting edits.
Similarly, locks and mute can prevent simultaneous access
to records or critical code sections.
So remember, memory-related vulnerabilities are weaknesses
in how applications manage and protect memory,
often allowing attackers
to cause system crashes, access sensitive data,
or execute harmful code.
Common memory vulnerabilities include the use
of deprecated functions, unsafe memory utilization,
overflows race conditions, and time of check to time
of use vulnerabilities.
Deprecated functions are outdated code elements
that lack modern security features,
making applications vulnerable
to attacks like buffer overflows.
Next, unsafe memory utilization occurs when applications
fail to properly manage memory allocation leading
to conditions like memory leaks
and buffer overflows, where attackers can gain access
to critical data or introduce malicious code.
Next overflows allow attackers
to manipulate memory in harmful ways, such
as in a buffer overflow, where attackers push data
beyond the buffer's boundary, potentially altering
adjacent memory or allowing code execution,
or in an integer overflow,
which involves exceeding the storage capacity
of a variable and can wrap data values
or change memory boundaries creates additional openings
for exploits.
Next, race conditions emerge when processes compete
to access or modify shared resources
without proper synchronization leading
to unpredictable outcomes when attackers exploit the timing
of those processes.
Finally, a specific type
of a race condition is called a time of check
to time of use vulnerability.
This occurs when an attacker manipulates data
after the system verifies a condition, but
before it executes an intended action,
allowing unauthorized access or data manipulation.
Overall, mitigating these vulnerabilities requires careful
code practices, such as using secure, updated functions,
employing memory validation techniques,
and implementing synchronization methods like locks
and mutexes to control resource access and timing.