...
 
Commits (2)
......@@ -4,6 +4,7 @@ var availableCommands = map[string]string{
"dns": "Perform a DNS lookup",
"mac": "Get the vendor of a MAC address",
"ping": "Perform a ping on a IPv4/IPv6 or domain",
"sshscan": "SSH security scan. Use only with permission",
"stats": "We all love statistics",
"traceroute": "Perform a traceroute",
"whoami": "Who am I?",
......
......@@ -26,7 +26,10 @@ func main() {
configFile, err := ioutil.ReadFile(`/etc/utelebot/config.json`)
if err != nil {
log.Println(err)
return
if configFile, err = ioutil.ReadFile(`config.json`); err != nil {
log.Println(err)
return
}
}
if err := json.Unmarshal(configFile, &config); err != nil {
log.Println(err)
......
package main
import (
"encoding/json"
"log"
"strconv"
"strings"
"git.bella.network/playground/golang/internal/dnsfetch"
"git.bella.network/playground/golang/internal/macfetch"
"git.bella.network/playground/golang/internal/pingcomm"
"git.bella.network/playground/golang/internal/sshscan"
"git.bella.network/playground/golang/internal/stats"
"git.bella.network/playground/golang/internal/traceroutecomm"
......@@ -82,11 +85,36 @@ func processMessage(bot *tgbotapi.BotAPI, update tgbotapi.Update) {
}
return
case "sshscan":
if len(args) == 0 || len(args[0]) == 0 {
msg.Text = sshscan.GetHelp()
} else if len(args) == 1 {
go bot.Send(tgbotapi.NewMessage(update.Message.Chat.ID, "Performing sshscan. This will take a few moments ..."))
msg.Text = sshscan.GetStringResponse(args[0], 22)
msg.ReplyToMessageID = update.Message.MessageID
} else {
port, err := strconv.ParseUint(args[1], 10, 16)
if err != nil {
msg.Text = "*ERROR* Invalid port given\n" + err.Error()
break
}
go bot.Send(tgbotapi.NewMessage(update.Message.Chat.ID, "Performing sshscan. This will take a few moments ..."))
msg.Text = sshscan.GetStringResponse(args[0], uint16(port))
msg.ReplyToMessageID = update.Message.MessageID
}
default:
// We don't know this command - skip it
return
}
bot.Send(msg)
// Try to send message. If it fails print response
if _, err := bot.Send(msg); err != nil {
// Convert message which should be sent to json for better output
jr, _ := json.Marshal(msg)
log.Println(err, string(jr))
// Send message that something went wrong
go bot.Send(tgbotapi.NewMessage(update.Message.Chat.ID, "Sorry!\nSomething went really wrong at this end and a bug report was created. :-/"))
}
} else {
stats.AddRecord(update)
}
......
package sshscan
import (
"bytes"
"encoding/json"
"log"
"net"
"os/exec"
"reflect"
"strconv"
"strings"
"time"
govalidator "github.com/asaskevich/govalidator"
)
// ScanResult contains a result of a scan
type ScanResult []struct {
SSHScanVersion string `json:"ssh_scan_version"`
IP string `json:"ip"`
Port int `json:"port"`
Hostname string `json:"hostname,omitempty"`
ServerBanner string `json:"server_banner,omitempty"`
SSHVersion interface{} `json:"ssh_version,omitempty"` // interface{} instead of float64 because "unknown" is printed on connection error
Os string `json:"os,omitempty"`
OsCpe string `json:"os_cpe,omitempty"`
SSHLib string `json:"ssh_lib,omitempty"`
SSHLibCpe string `json:"ssh_lib_cpe,omitempty"`
Cookie string `json:"cookie,omitempty"`
KeyAlgorithms []string `json:"key_algorithms,omitempty"`
EncryptionAlgorithmsClientToServer []string `json:"encryption_algorithms_client_to_server,omitempty"`
EncryptionAlgorithmsServerToClient []string `json:"encryption_algorithms_server_to_client,omitempty"`
MacAlgorithmsClientToServer []string `json:"mac_algorithms_client_to_server,omitempty"`
MacAlgorithmsServerToClient []string `json:"mac_algorithms_server_to_client,omitempty"`
CompressionAlgorithmsClientToServer []string `json:"compression_algorithms_client_to_server,omitempty"`
CompressionAlgorithmsServerToClient []string `json:"compression_algorithms_server_to_client,omitempty"`
LanguagesClientToServer []string `json:"languages_client_to_server,omitempty"`
LanguagesServerToClient []string `json:"languages_server_to_client,omitempty"`
AuthMethods []string `json:"auth_methods,omitempty"`
Fingerprints struct {
Dsa struct {
KnownBad string `json:"known_bad"`
Md5 string `json:"md5"`
Sha1 string `json:"sha1"`
Sha256 string `json:"sha256"`
} `json:"dsa,omitempty"`
Rsa struct {
KnownBad string `json:"known_bad"`
Md5 string `json:"md5"`
Sha1 string `json:"sha1"`
Sha256 string `json:"sha256"`
} `json:"rsa,omitempty"`
Ecdsa struct {
KnownBad string `json:"known_bad"`
Md5 string `json:"md5"`
Sha1 string `json:"sha1"`
Sha256 string `json:"sha256"`
} `json:"ecdsa,omitempty"`
Ed25519 struct {
KnownBad string `json:"known_bad"`
Md5 string `json:"md5"`
Sha1 string `json:"sha1"`
Sha256 string `json:"sha256"`
} `json:"ed25519,omitempty"`
} `json:"fingerprints"`
DuplicateHostKeyIps []interface{} `json:"duplicate_host_key_ips"`
Compliance struct {
Policy string `json:"policy"`
Compliant bool `json:"compliant"`
Recommendations []string `json:"recommendations"`
References []string `json:"references"`
Grade string `json:"grade"`
} `json:"compliance"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
ScanDurationSeconds float64 `json:"scan_duration_seconds"`
Error string `json:"error,omitempty"`
}
var privateIPBlocks []*net.IPNet
func init() {
for _, cidr := range []string{
"127.0.0.0/8", // IPv4 loopback
"10.0.0.0/8", // RFC1918
"172.16.0.0/12", // RFC1918
"192.168.0.0/16", // RFC1918
"::1/128", // IPv6 loopback
"fe80::/10", // IPv6 link-local
"fd00::/8", // IPv6 private address space
} {
_, block, _ := net.ParseCIDR(cidr)
privateIPBlocks = append(privateIPBlocks, block)
}
}
// GetHelp returns the help text of the command
func GetHelp() string {
return "*SSH Scan*\nPerform a ssh scan based on [Mozilla ssh_scan](https://github.com/mozilla/ssh_scan) to scan a target like your domain or your IP.\n*You must have permission to scan your target!*\n\nExamples:\n`/sshscan example.com`\n`/sshscan example.com 22`\n`/sshscan 192.168.1.1`\n\nAttention: This program uses the local system DNS resolver."
}
// GetStringResponse calls the scanTarget function and interprets the results
func GetStringResponse(target string, port uint16) string {
// Sleep for one second to delay response (Answer should not be sent before please wait message)
time.Sleep(time.Second)
if port <= 0 || port >= 65535 {
return "*ERROR*\nDestination port out of range"
}
// Determine sort of given target
if govalidator.IsIPv4(target) {
// We have an easteregg for you
if target == "127.0.0.1" {
return "*There is no place like 127.0.0.1*"
} else if isPrivateIP(net.ParseIP(target)) {
return "*Scan aborted*\nYou have no permission to scan a private IP address."
}
} else if govalidator.IsIPv6(target) {
if target == "::1" || target == "[::1]" {
return "*There is no place like ::1"
} else if isPrivateIP(net.ParseIP(target)) {
return "*Scan aborted*\nYou have no permission to scan a private IP address."
}
} else if !govalidator.IsDNSName(target) {
return "*Scan aborted*\nPlease provide a valid IPv4, IPv6 or domain name."
}
cmd := exec.Command("sh", "-c", "/usr/local/bin/ssh_scan --suppress-update-status -T 5 -t "+target+" -p "+strconv.FormatUint(uint64(port), 10))
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
// ssh_scan is creepy with responses
if strings.Contains(out.String(), "is not a valid target") {
return "*Scan aborted*\nTarget was not found or is not valid."
}
return "*ERROR*\nCan't get scan result. Is the domain valid?\n" + err.Error() + "\n" + out.String() + "\n" + stderr.String()
}
// Command returned help page, something went wrong
if strings.Contains(out.String(), "Usage: ssh_scan") {
return "*Scan aborted*\nTarget was not found or is not valid."
}
// Parse data from response
responseData := ScanResult{}
err = json.Unmarshal(out.Bytes(), &responseData)
if err != nil {
log.Println(err)
return "Something went wrong. Please try again later."
}
// Print title and target
text := "*SSH Scan result*\n\nTarget: `" + responseData[0].IP + ":" + strconv.Itoa(responseData[0].Port) + "`\n"
// Abort because of error
if responseData[0].Error != "" {
text += "*There was an error while scanning the target host:*\n"
if responseData[0].Error == "Socket is no longer valid" {
text += "Connection timed out"
} else {
text += responseData[0].Error
}
return text
}
// Convert version interface to string
sshVers := ""
if reflect.TypeOf(responseData[0].SSHVersion).String() == "string" {
sshVers = responseData[0].SSHVersion.(string)
} else if reflect.TypeOf(responseData[0].SSHVersion).String() == "float64" {
sshVers = strconv.FormatFloat(responseData[0].SSHVersion.(float64), 'f', 1, 64)
} else {
sshVers = "unknown type"
}
// Print server banner and version
text += "Server banner: `" + responseData[0].ServerBanner + "`\nSSH version: `" + sshVers + "`\nOS: `" + responseData[0].Os + "`\nCPE: `" + responseData[0].OsCpe + "`\nSSH-Lib: `" + responseData[0].SSHLib + "`\nSSH-Lib CPE: `" + responseData[0].SSHLibCpe + "`\n"
// Print if configuration is compliant
text += "\nCompliant: "
if responseData[0].Compliance.Compliant {
text += "*YES*"
} else {
text += "*NO*"
}
text += " - Grade: *" + responseData[0].Compliance.Grade + "*\n"
// Add recommendations
if len(responseData[0].Compliance.Recommendations) > 0 {
text += "*Recommendations:*\n"
for _, singleLine := range responseData[0].Compliance.Recommendations {
text += "- `" + singleLine + "`\n"
}
}
// Add references
if len(responseData[0].Compliance.References) > 0 {
text += "\n*References:*\n"
for _, singleLine := range responseData[0].Compliance.References {
text += singleLine + "\n"
}
}
// Add used policy
text += "\nUsed policy: " + responseData[0].Compliance.Policy
return text
}
func isPrivateIP(ip net.IP) bool {
for _, block := range privateIPBlocks {
if block.Contains(ip) {
return true
}
}
return false
}
......@@ -2,8 +2,8 @@ package stats
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"os/signal"
"sort"
......@@ -16,19 +16,25 @@ import (
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)
// MessageStat contains rwLock and a map of chats
type MessageStat struct {
Chat map[int64]SingleChat
rwLock sync.RWMutex
}
// SingleChat contains info about a single chat
type SingleChat struct {
User map[int]SingleUser
}
// SingleUser contains information about a single user
type SingleUser struct {
Message uint64
Date time.Time
Username string
}
// MessageData contains the entire statistics
var MessageData MessageStat
// Set data based on SingleUser data
......@@ -88,10 +94,10 @@ func init() {
// Load stats from disk to memory
statsFile, err := ioutil.ReadFile(`/var/lib/utelebot/stats.json`)
if err != nil {
fmt.Println(err)
log.Println(err)
} else {
if err := json.Unmarshal(statsFile, &MessageData.Chat); err != nil {
fmt.Println(err)
log.Println(err)
}
}
......@@ -117,7 +123,7 @@ func WriteData() {
stats, _ := json.Marshal(MessageData.GetAll())
err := ioutil.WriteFile(`/var/lib/utelebot/stats.json`, stats, 0644)
if err != nil {
fmt.Println(err)
log.Println(err)
}
}
......