Compare commits

...

27 commits

Author SHA1 Message Date
Augusto Dwenger J. b5c2560358 Replace deprecated ioutil.ReadFile with new func from os 2023-09-21 23:16:03 +02:00
Augusto Dwenger J. 61fc3ad141 Replace own ip extraction function with func from net package 2023-09-21 23:13:31 +02:00
Augusto Dwenger J. fd3e5530f9 Update crypto lib 2023-09-14 19:09:32 +02:00
Augusto Dwenger J. c12c77fb1d Update crypto lib 2022-09-24 17:05:36 +02:00
Augusto Dwenger J. fc116692a0 Increase project golang version to 1.19
Keep it up to date.
2022-08-11 18:16:32 +02:00
Augusto Dwenger J. 39a4ad47e1 Update crypto lib 2022-08-11 18:06:22 +02:00
Augusto Dwenger J. 7f421ecadb
Add a license section to the README 2022-05-21 16:15:44 +02:00
Augusto Dwenger J. ee5fd989b6
Update copyright holder information 2022-05-21 16:13:08 +02:00
Augusto Dwenger J. 1dc9ba16b5
Fix WaitGroup count on IPv4 only mode
It doesn't really matters, because we never mark them as done, but this
would make it easier if waiting for it would be required in the future.
2022-05-21 16:08:52 +02:00
Augusto Dwenger J. 8ebf7531e9
Move the ssh.ServerConfig creation to the main func
There is no need to create a config per request.
2022-05-21 16:08:32 +02:00
Augusto Dwenger J. f5a9473a9a
Extract previously inline PasswordCallback function into own function
I decided instead of defining the func inline in to a top level function
to reduce the nesting
2022-05-21 15:53:39 +02:00
Augusto Dwenger J. 52d38f1d36
Update go version in Dockerfile to 1.18 2022-05-21 15:51:57 +02:00
Augusto Dwenger J. 1a1119b9af
Increase project golang version 1.18
Keep it up to date.
2022-05-21 13:46:57 +02:00
Augusto Dwenger J. 9678d7ada2
Update crypto lib 2022-05-21 13:45:08 +02:00
Augusto Dwenger J. e11b8d9646 Update crypto lib 2022-03-16 11:47:28 +01:00
Augusto Dwenger J. b4a50e382f Fix variable naming inside the getKey function
This should fix golint.
2022-03-16 11:46:26 +01:00
Augusto Dwenger J. a1a5164061 Unexport PrintData function
There is no need to export the function.
2022-03-16 11:46:26 +01:00
Augusto Dwenger J. d20988a778 Update the crypto dependency 2022-01-17 19:46:54 +01:00
Augusto Dwenger J. fc61e9f469
Adjust README section titel heights 2021-09-02 12:58:57 +02:00
Augusto Dwenger J. 26baa83da8
Add JSON logging option
The connection information can now be displayed in JSON format. To
activate it use the '--json' flag. It will only format the connection
information in JSON everything else will be still in plain text.
2021-09-02 12:54:21 +02:00
Augusto Dwenger J. a63e9965f2
Fix not loading key from path if given as param 2021-09-02 12:17:22 +02:00
Augusto Dwenger J. a3eddde736
Update crypto dependency to newest version 2021-09-02 12:11:59 +02:00
Augusto Dwenger J. 74b456241a Add Docker setup 2021-09-01 23:02:37 +02:00
Augusto Dwenger J. b345d17602 Merge branch 'systemd'
Adds a systemd service setup for sshlog.

* systemd:
  Add information about util directory
  Add a README inside utils with systemd instructions
  Add sshlog.service configuration
2021-07-28 19:57:41 +02:00
Augusto Dwenger J. abdaa65cfa
Add information about util directory 2021-06-25 20:52:23 +02:00
Augusto Dwenger J. c4f38cd7e1 Add a README inside utils with systemd instructions
This README should hold all infromation for all utilities that should
appeare around sshlog, like the systemd service configuration.
2021-06-18 21:26:20 +02:00
Augusto Dwenger J. 279b3fa159 Add sshlog.service configuration
I am not an expert in systemd service creation/configuration. I just
took an example file from the internet and modified it slightly to match
my setup.
This configuration gets using the `systemd-analyze` a mixed result while
scanning it with the focus on security. I am sure it can be optimize to
have a lot less capabilities but for the time I don't know how.

Refs:
 - https://medium.com/@benmorel/creating-a-linux-service-with-systemd-611b5c8b91d6
 - https://www.redhat.com/sysadmin/systemd-secure-services
2021-06-18 21:24:26 +02:00
10 changed files with 146 additions and 68 deletions

3
.dockerignore Normal file
View file

@ -0,0 +1,3 @@
*
!go.*
!*.go

15
Dockerfile Normal file
View file

@ -0,0 +1,15 @@
FROM docker.io/golang:1.19 AS build
WORKDIR /src
COPY go.* /src/
RUN go mod download
COPY * /src/
RUN CGO_ENABLED=0 go build -o /sshlog .
# Final image
FROM scratch
COPY --from=build /sshlog /
ENTRYPOINT ["/sshlog", "-p", "2222", "-4"]
EXPOSE 2222

View file

@ -1,4 +1,4 @@
MIT License Copyright (c) 2021 Augusto Dwenger J.
MIT License Copyright (c) 2021-2022 Augusto Dwenger J.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -4,7 +4,7 @@ A small tool to log IPs, usernames and passwords from incoming ssh-auth requests
It opens a minimal SSH-Server and listens on IPv4 and IPv6 for auth requests.
The goal of this little tool is to log the requests coming from bots living inside the wild internet.
# Install
## Install
Make sure you have Golang installed and configured.
```shell
git clone https://git.sr.ht/~hamburghammer/sshlog
@ -13,7 +13,7 @@ go build
```
Now you should be able to execute the newly generated executable with `./sshlog`.
# Usage
## Usage
Start with:
```shell
sshlog -p 2222
@ -27,7 +27,15 @@ Output:
2021/06/02 23:08:54 SRC=127.0.0.1 USERNAME=test PASSWORD=fooof
```
## Options
Output with `--json`:
```text
2021/09/02 12:43:42 Starting ssh logger on port 2222...
{"date": "2021-09-02T12:44:15+02:00", "src": "127.0.0.1", "username": "test", "password": "foo"}
{"date": "2021-09-02T12:44:18+02:00", "src": "127.0.0.1", "username": "test", "password": "foof"}
{"date": "2021-09-02T12:44:21+02:00", "src": "127.0.0.1", "username": "test", "password": "fooof"}
```
### Options
```text
A small tool to log IPs, usernames and passwords from incoming ssh-auth requests.
@ -36,8 +44,15 @@ USAGE:
FLAGS:
-h, --help Prints this help message and exits.
--json Log in JSON instead of plain text.
-k, --key string Path to the host key for the ssh server.
If absent it will automatically generate a new one for each run.
-4, --onlyIPv4 Only listens on IPv4.
-p, --port string Port to listen for incoming connections. (default "22"))
```
## Utils
Inside the `util` directory you might find some additional information like how to create Systemd service for sshlog.
## License
This project is being licensed under the [MIT license](LICENSE).

6
go.mod
View file

@ -1,8 +1,10 @@
module git.sr.ht/~hamburghammer/sshlog
go 1.16
go 1.19
require (
github.com/spf13/pflag v1.0.5
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
golang.org/x/crypto v0.13.0
)
require golang.org/x/sys v0.12.0 // indirect

14
go.sum
View file

@ -1,11 +1,7 @@
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=

74
main.go
View file

@ -6,12 +6,11 @@ import (
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"log"
"net"
"os"
"strings"
"sync"
"time"
flags "github.com/spf13/pflag"
@ -23,6 +22,7 @@ var (
isHelp bool
keyPath string
onlyIPv4 bool
isJson bool
)
func init() {
@ -30,6 +30,7 @@ func init() {
flags.StringVarP(&keyPath, "key", "k", "", "Path to the host key for the ssh server.\nIf absent it will automatically generate a new one for each run.")
flags.BoolVarP(&isHelp, "help", "h", false, "Prints this help message and exits.")
flags.BoolVarP(&onlyIPv4, "onlyIPv4", "4", false, "Only listens on IPv4.")
flags.BoolVar(&isJson, "json", false, "Log in JSON instead of plain text.")
flags.Parse()
}
@ -46,6 +47,9 @@ func main() {
log.Fatal("Failed to parse private key")
}
serverConfig := ssh.ServerConfig{PasswordCallback: printConnectionData}
serverConfig.AddHostKey(hostKey)
log.Printf("Starting ssh logger on port %s...\n", port)
ipv4Listener, err := net.Listen("tcp", "0.0.0.0:"+port)
if err != nil {
@ -54,8 +58,8 @@ func main() {
defer ipv4Listener.Close()
wg := new(sync.WaitGroup)
wg.Add(2)
go startAccepting(ipv4Listener, hostKey)
wg.Add(1)
go startAccepting(ipv4Listener, serverConfig)
if !onlyIPv4 {
ipv6Listener, err := net.Listen("tcp", "[::1]:"+port)
@ -64,60 +68,77 @@ func main() {
}
defer ipv6Listener.Close()
go startAccepting(ipv6Listener, hostKey)
wg.Add(1)
go startAccepting(ipv6Listener, serverConfig)
}
wg.Wait() // Waits until it gets terminated
}
func startAccepting(listener net.Listener, hostKey ssh.Signer) {
func startAccepting(listener net.Listener, serverConfig ssh.ServerConfig) {
for {
con, err := listener.Accept()
if err != nil {
log.Println(err)
}
go PrintData(con, hostKey)
go connectionHandler(con, serverConfig)
}
}
func getKey(path string) []byte {
if path != "" {
privateBytes, err := ioutil.ReadFile("private-key.pem")
privateBytes, err := os.ReadFile(path)
if err != nil {
log.Fatalf("Failed to load private key (%s)", path)
log.Fatalf("Failed to load private key: %s", path)
}
return privateBytes
}
privatekey, err := rsa.GenerateKey(rand.Reader, 2048)
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
log.Fatalf("Cannot generate RSA key\n")
}
privkey_bytes := x509.MarshalPKCS1PrivateKey(privatekey)
privkey_pem := pem.EncodeToMemory(
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privkey_bytes,
Bytes: privateKeyBytes,
},
)
return privkey_pem
return privateKeyPem
}
func PrintData(con net.Conn, hostKey ssh.Signer) {
serverConfig := ssh.ServerConfig{PasswordCallback: func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
log.Printf("SRC=%s USERNAME=%s PASSWORD=%s\n", getIPWithoutPort(conn.RemoteAddr().String()), conn.User(), string(password))
return nil, fmt.Errorf("password rejected for %q", conn.User())
}}
serverConfig.AddHostKey(hostKey)
func connectionHandler(con net.Conn, serverConfig ssh.ServerConfig) {
_, _, _, err := ssh.NewServerConn(con, &serverConfig)
if err != nil {
return
}
}
func printConnectionData(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
responseErr := fmt.Errorf("password rejected for %s", conn.User())
ip, _, err := net.SplitHostPort(conn.RemoteAddr().String())
if err != nil {
fmt.Println(err)
return nil, responseErr
}
if isJson {
fmt.Printf(
"{\"date\": \"%s\", \"src\": \"%s\", \"username\": \"%s\", \"password\": \"%s\"}\n",
time.Now().Format(time.RFC3339),
ip,
conn.User(),
string(password),
)
} else {
log.Printf("SRC=%s USERNAME=%s PASSWORD=%s\n", ip, conn.User(), string(password))
}
return nil, responseErr
}
func printHelp() {
fmt.Println(`A small tool to log IPs, usernames and passwords from incoming ssh-auth requests.
@ -127,10 +148,3 @@ USAGE:
FLAGS:`)
flags.PrintDefaults()
}
func getIPWithoutPort(address string) string {
if strings.Contains(address, "]") { // Is IPv6
return strings.ReplaceAll(strings.Split(address, "]")[0], "[", "")
}
return strings.Split(address, ":")[0]
}

View file

@ -1,23 +0,0 @@
package main
import "testing"
func TestGetIPWithoutPort(t *testing.T) {
t.Run("IPv6", func(t *testing.T) {
got := getIPWithoutPort("[::1]:2222")
want := "::1"
if want != got {
t.Fatalf("Want '%s' but got '%s'", want, got)
}
})
t.Run("IPv4", func(t *testing.T) {
got := getIPWithoutPort("127.0.0.1:2222")
want := "127.0.0.1"
if want != got {
t.Fatalf("Want '%s' but got '%s'", want, got)
}
})
}

15
util/README.md Normal file
View file

@ -0,0 +1,15 @@
# Utility
A collection of scripts and useful configurations.
## Systemd
You can use the example `sshlog.service` to run `sshlog` as a service under Systemd.
Copy the `sshlog.service` into `/etc/systemd/system/sshlog.service` and change the `ExecStart` value to point to the `sshlog` executable.
If you installed it with `go install` it will be propably located inside your `$GOPATH/bin` directory. Use the full path as
value for the `ExecStart` parameter. This is also the location if you wannt to give it some arguments like `-p 2222` for the listening port.
After saving the configuration under `/etc/systemd/system/sshlog.service` it can be started with `systemctl start sshlog.service` and
enabled to run on system boot with `systemctl enable sshlog.service`
While running it with Systemd all logs can be accesst with `journalctl -u sshlog.service`.

41
util/sshlog.service Normal file
View file

@ -0,0 +1,41 @@
[Unit]
Description=SSH Auth Logging
Requires=network-online.target
[Service]
Type=simple
Restart=always
RestartSec=30sec
# Path to the executable
ExecStart=/home/user/go/bin/sshlog -p 2222
KillSignal=SIGTERM
# Stop trying to restart the service if it restarts too many times in a row
StartLimitInterval=5min
StartLimitBurst=4
StandardOutput=journal
StandardError=journal
StandardInput=null
DynamicUser=yes
PrivateTmp=true
PrivateDevices=true
PrivateMounts=true
ProtectSystem=full
#ProtectHome=true
RestrictNamespaces=true
InaccessiblePaths=/run /var /etc
PrivateUsers=true
NoNewPrivileges=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
MemoryDenyWriteExecute=true
[Install]
WantedBy=multi-user.target