mirror of
https://git.sr.ht/~hamburghammer/sshlog
synced 2024-05-20 01:54:37 +02:00
Compare commits
27 commits
Author | SHA1 | Date | |
---|---|---|---|
Augusto Dwenger J. | b5c2560358 | ||
Augusto Dwenger J. | 61fc3ad141 | ||
Augusto Dwenger J. | fd3e5530f9 | ||
Augusto Dwenger J. | c12c77fb1d | ||
Augusto Dwenger J. | fc116692a0 | ||
Augusto Dwenger J. | 39a4ad47e1 | ||
Augusto Dwenger J. | 7f421ecadb | ||
Augusto Dwenger J. | ee5fd989b6 | ||
Augusto Dwenger J. | 1dc9ba16b5 | ||
Augusto Dwenger J. | 8ebf7531e9 | ||
Augusto Dwenger J. | f5a9473a9a | ||
Augusto Dwenger J. | 52d38f1d36 | ||
Augusto Dwenger J. | 1a1119b9af | ||
Augusto Dwenger J. | 9678d7ada2 | ||
Augusto Dwenger J. | e11b8d9646 | ||
Augusto Dwenger J. | b4a50e382f | ||
Augusto Dwenger J. | a1a5164061 | ||
Augusto Dwenger J. | d20988a778 | ||
Augusto Dwenger J. | fc61e9f469 | ||
Augusto Dwenger J. | 26baa83da8 | ||
Augusto Dwenger J. | a63e9965f2 | ||
Augusto Dwenger J. | a3eddde736 | ||
Augusto Dwenger J. | 74b456241a | ||
Augusto Dwenger J. | b345d17602 | ||
Augusto Dwenger J. | abdaa65cfa | ||
Augusto Dwenger J. | c4f38cd7e1 | ||
Augusto Dwenger J. | 279b3fa159 |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
*
|
||||
!go.*
|
||||
!*.go
|
15
Dockerfile
Normal file
15
Dockerfile
Normal 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
|
2
LICENSE
2
LICENSE
|
@ -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
|
||||
|
|
21
README.md
21
README.md
|
@ -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
6
go.mod
|
@ -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
14
go.sum
|
@ -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
74
main.go
|
@ -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]
|
||||
}
|
||||
|
|
23
main_test.go
23
main_test.go
|
@ -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
15
util/README.md
Normal 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
41
util/sshlog.service
Normal 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
|
Loading…
Reference in a new issue