Last active
December 23, 2024 23:41
-
-
Save samvv/8aab623e4e1f37ea7cec201fd34a32ff to your computer and use it in GitHub Desktop.
Broken SSH server example
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
| // Copyright 2011 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package main | |
| import ( | |
| "fmt" | |
| "log" | |
| "net" | |
| "os" | |
| "sync" | |
| "golang.org/x/crypto/ssh" | |
| ) | |
| func main() { | |
| // Public key authentication is done by comparing | |
| // the public key of a received connection | |
| // with the entries in the authorized_keys file. | |
| authorizedKeysBytes, err := os.ReadFile("authorized_keys") | |
| if err != nil { | |
| log.Fatalf("Failed to load authorized_keys, err: %v", err) | |
| } | |
| authorizedKeysMap := map[string]bool{} | |
| for len(authorizedKeysBytes) > 0 { | |
| pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| authorizedKeysMap[string(pubKey.Marshal())] = true | |
| authorizedKeysBytes = rest | |
| } | |
| // An SSH server is represented by a ServerConfig, which holds | |
| // certificate details and handles authentication of ServerConns. | |
| config := &ssh.ServerConfig{ | |
| // Remove to disable password auth. | |
| PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { | |
| // Should use constant-time compare (or better, salt+hash) in | |
| // a production setting. | |
| if c.User() == "testuser" && string(pass) == "tiger" { | |
| return nil, nil | |
| } | |
| return nil, fmt.Errorf("password rejected for %q", c.User()) | |
| }, | |
| // Remove to disable public key auth. | |
| PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) { | |
| if authorizedKeysMap[string(pubKey.Marshal())] { | |
| return &ssh.Permissions{ | |
| // Record the public key used for authentication. | |
| Extensions: map[string]string{ | |
| "pubkey-fp": ssh.FingerprintSHA256(pubKey), | |
| }, | |
| }, nil | |
| } | |
| return nil, fmt.Errorf("unknown public key for %q", c.User()) | |
| }, | |
| } | |
| privateBytes, err := os.ReadFile("id_rsa") | |
| if err != nil { | |
| log.Fatal("Failed to load private key: ", err) | |
| } | |
| private, err := ssh.ParsePrivateKey(privateBytes) | |
| if err != nil { | |
| log.Fatal("Failed to parse private key: ", err) | |
| } | |
| config.AddHostKey(private) | |
| // Once a ServerConfig has been configured, connections can be | |
| // accepted. | |
| listener, err := net.Listen("tcp", "0.0.0.0:2022") | |
| if err != nil { | |
| log.Fatal("failed to listen for connection: ", err) | |
| } | |
| nConn, err := listener.Accept() | |
| if err != nil { | |
| log.Fatal("failed to accept incoming connection: ", err) | |
| } | |
| // Before use, a handshake must be performed on the incoming | |
| // net.Conn. | |
| conn, chans, reqs, err := ssh.NewServerConn(nConn, config) | |
| if err != nil { | |
| log.Fatal("failed to handshake: ", err) | |
| } | |
| log.Printf("logged in with key %s", conn.Permissions.Extensions["pubkey-fp"]) | |
| var wg sync.WaitGroup | |
| defer wg.Wait() | |
| // The incoming Request channel must be serviced. | |
| wg.Add(1) | |
| go func() { | |
| ssh.DiscardRequests(reqs) | |
| wg.Done() | |
| }() | |
| // Service the incoming Channel channel. | |
| for newChannel := range chans { | |
| // Channels have a type, depending on the application level | |
| // protocol intended. In the case of a shell, the type is | |
| // "session" and ServerShell may be used to present a simple | |
| // terminal interface. | |
| if newChannel.ChannelType() != "session" { | |
| newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") | |
| continue | |
| } | |
| channel, requests, err := newChannel.Accept() | |
| if err != nil { | |
| log.Fatalf("Could not accept channel: %v", err) | |
| } | |
| // Sessions have out-of-band requests such as "shell", | |
| // "pty-req" and "env". Here we handle only the | |
| // "shell" request. | |
| wg.Add(1) | |
| go func(in <-chan *ssh.Request) { | |
| for req := range in { | |
| switch req.Type { | |
| case "exec": | |
| channel.Stderr().Write([]byte("Request received!\n")) | |
| req.Reply(true, nil) | |
| channel.Close() | |
| default: | |
| req.Reply(false, nil) | |
| } | |
| } | |
| wg.Done() | |
| }(requests) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment