Puzzleshot #021 - Rewriting proxy with sed
A change of pace this round — less data validation, more protocol-level troubleshooting.
The scenario:
You have a device (in this case, inspired by a real problem with a 3D printer’s built-in FTP server) that returns a broken response when a client tries to enter passive mode for file transfer:
227 Entering Passive Mode (0,0,0,0,p1,p2)
That 0,0,0,0 is supposed to be the server’s IP address, encoded as four comma-separated octets. Instead of a real, routable IP, the device sends all zeros — which most FTP clients simply can’t work with, since there’s no address to actually connect to for the data channel.
The “normal” fix would be patching the FTP client’s source code to special-case this response. But you don’t want to modify or rebuild client software — you just want the broken response fixed in transit, before the client ever sees it.
The challenge:
The FTP control channel is plain, line-oriented text — which means a tool like sed can read and rewrite it on the fly. Using socat (a more flexible relative of netcat) and sed together, how would you build a lightweight local proxy that:
- Listens on a local port and forwards traffic to the real FTP server
- Intercepts the 227 response and rewrites 0,0,0,0 with the server’s actual LAN IP
- Passes everything else through unchanged
A starting point — socat can act as a listener and forwarder in one command, and pipe its stream through another process:
socat TCP-LISTEN:2121,fork "EXEC:'...'"
Things to consider:
- Why does this only work because FTP’s control channel is plain text?
- What happens if sed buffers its output instead of processing it immediately?
- What are the limits of this approach — where would it break down?
Reveal solution
This challenge involves fixing a broken FTP passive mode response in transit, without touching any client software.
The full solution:
socat TCP-LISTEN:2121,fork \
"EXEC:'socat - TCP:192.168.1.50:21 | sed -u \"s/227 Entering Passive Mode (0,0,0,0,/227 Entering Passive Mode (192,168,1,50,/g\"'"
Then point your FTP client at localhost:2121 instead of the device directly.
What’s happening:
socat TCP-LISTEN:2121,fork starts a local listener on port 2121. fork means it spawns a new handler for each incoming connection rather than only accepting one ever.
Inside that listener, EXEC:'...' runs a second socat process that connects out to the real FTP server (in this case 192.168.1.50:21) and pipes its output through sed before sending it back to the client.
sed -u "s/227 Entering Passive Mode (0,0,0,0,/227 Entering Passive Mode (192,168,1,50,/g"
This is the actual fix — it looks for the broken 0,0,0,0 pattern specifically inside a 227 response and replaces it with the server’s real LAN IP in the same comma-separated octet format FTP expects. Every other line in the stream passes through completely unchanged.
Why this only works because FTP control is plain text:
FTP’s control channel is a simple line-oriented text protocol — commands and responses are just readable ASCII lines terminated by CRLF. That’s exactly what sed is built to process. If this were a binary or encrypted protocol, this approach wouldn’t work at all — there’d be nothing readable for sed to pattern-match against.
Why -u matters:
sed buffers its output by default, which is fine for files but a problem for a live streaming connection — the FTP session would stall waiting for enough data to trigger a flush. -u forces line-by-line unbuffered processing, so the response gets rewritten and forwarded immediately rather than sitting in a buffer.
Where this breaks down:
This approach only touches the control channel — the actual data transfer connections go directly between the client and server using the corrected IP, so nothing about the file transfer itself is proxied or slowed down. That’s a feature, not a limitation.
It is fragile in a few ways though: it assumes the 0,0,0,0 pattern only ever appears in this exact context (a reasonable assumption for this specific device, but not guaranteed for FTP generally), and it requires manually specifying the server’s LAN IP rather than discovering it dynamically. It’s a targeted fix for a known, narrow problem — not a general-purpose FTP proxy.
The broader point:
Sometimes the cleanest fix for a broken protocol response isn’t patching the client or the server — it’s rewriting the conversation between them. When a protocol is plain text and line-oriented, a small, disposable proxy built from unix tools can solve a real problem without touching a single line of application code.