|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: Redirectors - he's here he's there he's every where |
| 4 | +subtitle: Bouncing connections! |
| 5 | +gh-repo: spellshift/realm |
| 6 | +gh-badge: [star, fork, follow] |
| 7 | +tags: [tavern, infra] |
| 8 | +comments: true |
| 9 | +mathjax: true |
| 10 | +author: Hulto |
| 11 | +--- |
| 12 | + |
| 13 | +## The need for redirectors |
| 14 | + |
| 15 | +Why do we even need a redirector? |
| 16 | + |
| 17 | +Redirectors provide three main benefits in command and control infrastructure. |
| 18 | + |
| 19 | + |
| 20 | +First, they obfuscate the true location of the C2 server by acting as an intermediary layer that masks the actual backend infrastructure from defenders and security tools attempting to identify and block malicious activity. |
| 21 | + |
| 22 | + |
| 23 | +Second, they enable architectural flexibility by allowing a single backend server to operate behind multiple domains, IP addresses, and hosting providers, making it significantly more difficult to completely disrupt the C2 infrastructure through blocking or takedown attempts. |
| 24 | + |
| 25 | + |
| 26 | +Lastly we want to avoid direct access to a C2 framework to both minimize fingerprinting as well as prevent attacks on the control plane. By introducing redirectors we can now easily place the tavern C2 server in a private VPC behind a VPN or Identity Aware Proxy (IAP) |
| 27 | + |
| 28 | +## The need for multiple transports |
| 29 | + |
| 30 | +Supporting multiple transport protocols is essential for maintaining operational security and ensuring reliable communication channels. |
| 31 | + |
| 32 | + |
| 33 | +By implementing diverse transport mechanisms, we can avoid generating easily identifiable network traffic patterns that security tools and analysts could use to detect and classify our C2 communications as malicious. |
| 34 | + |
| 35 | + |
| 36 | +Additionally, multiple transports provide critical flexibility in restricted network environments where certain protocols may be blocked or heavily monitored, allowing agents to adapt and find alternative communication paths back to the C2 infrastructure. |
| 37 | + |
| 38 | + |
| 39 | +## Implementation |
| 40 | +Killing two birds with one stone we've created a new sub-command in tavern `redirector`. |
| 41 | +This allows you to do traditional redirection using the `./tavern redirector --grpc` command to forward grpc connections from the current host to an upstream Tavern server. |
| 42 | + |
| 43 | +But we've also added a redirector per transport allowing developers to have full control over the transport from agent to server. Currently we've only implemented HTTP1 but are looknig forward to adding more. |
| 44 | + |
| 45 | +In order to keep Realm's architecture simple we decided that agents should use Tavern's public key to encrypt traffic instead of each redirector having their own keys. This makes key management simpler but means that the redirector can't unmarshall the encrypted messages. Since GRPC wants all data to be strongly typed (usually a very good thing) this meant we needed a creative solution to copying the raw grpc bytes. This custom codec overrides the Marshall / Unmarshall steps instead returning raw bytes. This allows the redirector to pass the encyrpted protobufs along with no knowledege of the proto spec. |
| 46 | + |
| 47 | +```go |
| 48 | +type RawCodec struct{} |
| 49 | + |
| 50 | +func (RawCodec) Marshal(v any) ([]byte, error) { |
| 51 | + if b, ok := v.([]byte); ok { |
| 52 | + return b, nil |
| 53 | + } |
| 54 | + return nil, fmt.Errorf("failed to marshal, message is %T", v) |
| 55 | +} |
| 56 | + |
| 57 | +func (RawCodec) Unmarshal(data []byte, v any) error { |
| 58 | + if b, ok := v.(*[]byte); ok { |
| 59 | + *b = data |
| 60 | + return nil |
| 61 | + } |
| 62 | + return fmt.Errorf("failed to unmarshal, message is %T", v) |
| 63 | +} |
| 64 | + |
| 65 | +func (RawCodec) Name() string { |
| 66 | + return "raw" |
| 67 | +} |
| 68 | + |
| 69 | +func init() { |
| 70 | + encoding.RegisterCodec(RawCodec{}) |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +Another unusual issue we encountered when writing the redirector's upstream connection is that grpc prefers ipv6 but many networks (my home network included) don't support IPv6. The GRPC client will use only ipv6 if a AAAA record is present and GCP Cloud Run provisions AAAA records for workloads. To get around this we added a custom dialer to the client that manually resolves, and connects to the upstream tavern server. |
| 75 | + |
| 76 | +```go |
| 77 | + conn, err := grpc.NewClient( |
| 78 | + url.Host, |
| 79 | + grpc.WithTransportCredentials(tc), |
| 80 | + grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { |
| 81 | + // Resolve using IPv4 only (A records, not AAAA records) |
| 82 | + ips, err := net.DefaultResolver.LookupIP(ctx, "ip4", url.Hostname()) |
| 83 | + if err != nil { |
| 84 | + return nil, err |
| 85 | + } |
| 86 | + if len(ips) == 0 { |
| 87 | + return nil, fmt.Errorf("no IPv4 addresses found for %s", url.Hostname()) |
| 88 | + } |
| 89 | + |
| 90 | + // Force IPv4 by using "tcp4" instead of "tcp" |
| 91 | + dialer := &net.Dialer{} |
| 92 | + tcpConn, err := dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ips[0].String(), port)) |
| 93 | + if err != nil { |
| 94 | + return nil, err |
| 95 | + } |
| 96 | + |
| 97 | + return tcpConn, nil |
| 98 | + }), |
| 99 | + ) |
| 100 | +``` |
| 101 | + |
| 102 | +This forces IPv4, if we encounter a situation where only IPv6 is available we'll need to revist this and add some form of failover but for now it's working. |
0 commit comments