In this lesson
we're going to talk about race conditions
and the vulnerabilities associated with them.
Now, what exactly is a race condition?
Well, a race condition is a software vulnerability
that occurs when the resulting outcome from executing
processes is directly dependent on the order
and timing of certain events,
and those events failed to execute in the order
and timing that was intended by the developer.
Essentially, this is a complicated way of saying
that the computer is unexpectedly trying
to race itself into processing certain types of data
and you get an unexpected result.
For example, if you're trying to do something legitimate
and the attackers try to do something malicious
at the same time, they might be able to get their request in
before yours so that they can actually take advantage
of a race condition vulnerability in the program's code
to run their processes before you can run yours.
Race condition vulnerabilities are found when there are
multiple threads attempting to write
to a particular variable or object
at the same memory location at the same time.
One of the ways this can happen is by a dereference
or a no-pointer dereference exploit that's being exploited
to trigger that race condition.
Dereferencing is a software vulnerability
that occurs when the code attempts
to remove the relationship between a pointer
and the thing that pointer is pointing to
inside of the memory.
Dereferencing is going to break the reference pairing
between the pointer and that thing that's being pointed
to in memory, and this allows changes to that location
of memory or the variable that contain that memory.
Race conditions are really difficult to detect
and they're really difficult to mitigate
inside of modern networks.
The Dirty COW exploit, for example,
was an extremely popular exploit in 2016
that's an excellent example of a race condition
that's being exploited in the real world.
COW is actually an acronym
and it stands for copy-on-write.
In the Dirty COW exploit, Linux Operating Systems
and some Android devices were being exploited
to allow a local privilege escalation to occur
by exploiting a race condition vulnerability
based on the implementation of the programming
for the copy-on-write function that was used inside
of the kernel's memory management system.
Because of this race condition that existed in the code,
if an attacker used the right timing,
they could actually exploit the copy-on-write mechanism
to turn the read-only mapping
of a file into a write mapping.
This would then allow the attacker
to have escalated privileges
because they could write to a file even though they only
were supposed to have read-only access.
Now, what made this exploit even more dangerous is
that it was difficult to detect
because it didn't even leave anything inside the system logs
to let you know that it was happening.
This is why race conditions become really dangerous
'cause they often happen outside
of our normally logged processes, inside of our system,
and therefore we can't see that they've happened.
While the Dirty COW vulnerability allowed a race condition
against memory management systems to occur,
race conditions can also be used against databases
and your file systems.
When race conditions are used to exploit a database
or file system, they're generally going to take the form
of a time-of-check to time-of-use vulnerability
known as a TOC/TOU vulnerability.
Now a time-of-check/time-of-use vulnerability occurs
when there's a change between when the application
checks a resource
and when the application actually uses that resource.
Essentially, when a vulnerability
is going to be exploited here,
it's going to allow us to change
or invalidate the check that was previously made.
Think about it this way.
If the attacker can identify the time the check happened
and then they do something before it was actually used,
that becomes a race condition that they can exploit.
And then they can manipulate the data
after it's already been checked,
but before the application uses it.
To protect against a race condition like this,
you can use locks and Mutexes
to lock resources while a process is being run.
Now, a Mutex is a mutually exclusive flag,
and this acts as a gatekeeper to a section of code
so that only one thread can be processed at a time.
And this will help prevent a race condition
that would otherwise be caused by concurrent execution
of that code or function.
Now, most databases have a method that can be used
to lock the resource while it's being processed.
If you've ever used SharePoint at work
to access a particular file for editing, for example,
it actually puts a lock on that file
so others can read it while you're editing it,
but nobody else can check out that file
for editing while you have it in use.
This prevents two users from changing the file
at the same time and it helps to maintain version control.
This same concept is going to be used
to lock out a record in a database
or by using a Mutex to lock out the execution
of a function or part of the code
until the other threat is completed.
Overall, the uses of locks
and Mutexes work really well to prevent race conditions,
but sometimes they can cause a deadlock to occur.
Now, a deadlock occurs when a lock takes effect,
but the process that set that lock is waiting for it
to be terminated, crashes,
or simply doesn't finish properly,
and that causes the lock to not be removed
from that resource, even though the processing
was already completed.
When this occurs,
the system or code can lose access to that resource
until that lock is overridden or manually removed.
To prevent deadlocking, it's important
that you properly design and test any locks or Mutexes
that you're going to be using
to prevent a race condition in your code.