Skip to content

Commit

Permalink
Merge pull request #4 from blasrodri/connection-analysis
Browse files Browse the repository at this point in the history
Connection analysis
  • Loading branch information
blasrodri authored May 11, 2020
2 parents 5860399 + cfc86fd commit c50330f
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 26 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# FROWN

![forwn-screencast](frown.gif)

Frown provides a user-friendly interface to visualize all open connections that
a user has at a certain moment. For each of them, it performs a security check
and reports the ones that might be non-secure. Think of it as a `lsof`, for
Expand Down
14 changes: 8 additions & 6 deletions dns/dns.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dns

import (
"fmt"
"net"
"strings"
)
Expand All @@ -9,6 +10,11 @@ func getIpAddr(ipAddr *net.IP) ([]string, error) {
return net.LookupAddr(ipAddr.String())
}

var KnownDomains = map[string]string{
"compute.amazonaws.com": "Amazon AWS",
"1e100.net": "Google Cloud",
}

func DomainName(ipAddr *net.IP) (string, error) {
domains, err := getIpAddr(ipAddr)
if err != nil {
Expand All @@ -23,14 +29,10 @@ func DomainName(ipAddr *net.IP) (string, error) {
}

func getKnownDomain(dnsDomains []string) (string, error) {
knownDomains := map[string]string{
"compute.amazonaws.com": "Amazon AWS",
"1e100.net": "Google Cloud",
}
for _, domain := range dnsDomains {
for knownDom, niceName := range knownDomains {
for knownDom, niceName := range KnownDomains {
if strings.HasSuffix(strings.TrimSuffix(domain, "."), knownDom) {
return niceName, nil
return fmt.Sprintf("%s (%s)", knownDom, niceName), nil
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions dns/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ func TestgetKnownDomain(t *testing.T) {
knownDomain, _ := getKnownDomain([]string{"ec2-52-38-207-226.us-west-2.compute.amazonaws.com"})
assert.Equal(t, "Amazon AWS", knownDomain)
knownDomain, _ = getKnownDomain([]string{"arn09s10-in-f4.1e100.net"})
assert.Equal(t, "Google Cloud", knownDomain)
assert.Equal(t, "1e100.net (Google Cloud)", knownDomain)
}

func TestDomainName(t *testing.T) {
ip := net.ParseIP("172.217.22.174")
domName, _ := DomainName(&ip)
assert.Equal(t, "Google Cloud", domName)
assert.Equal(t, "1e100.net (Google Cloud)", domName)

}
Binary file added frown.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 7 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import (
"github.com/blasrodri/frown/ui"
)


func main() {
manageState(ui.TerminalUI)
//manageState(debug)

var uiConfig = &ui.UIConfig {
FilterSecurityLevel: 1,

}
manageState(uiConfig, ui.TerminalUI)
}
11 changes: 6 additions & 5 deletions state.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"github.com/blasrodri/frown/ui"
"github.com/blasrodri/frown/lsof"
"github.com/blasrodri/frown/stats"
"log"
Expand Down Expand Up @@ -84,7 +85,7 @@ func (c *connectionsState) setOpenSockets(pid int, listOpenSockets []lsof.Socket
}
}

func manageState(uiFunc func(<-chan *stats.Report, chan<- bool)) {
func manageState(config *ui.UIConfig, uiFunc func(*ui.UIConfig, <-chan *stats.Report, chan<- bool)) {
state := newConnectionState()
processesChan := make(chan []*lsof.Process)
connectionsChan := make(chan []*lsof.ConnectionDetails)
Expand All @@ -93,7 +94,7 @@ func manageState(uiFunc func(<-chan *stats.Report, chan<- bool)) {
go manageProcceses(processesChan)
go manageConnections(connectionsChan)
go reportSats(state, reportChan)
go uiFunc(reportChan, closeChan)
go uiFunc(config, reportChan, closeChan)

var shouldStop = false

Expand Down Expand Up @@ -139,7 +140,7 @@ func manageState(uiFunc func(<-chan *stats.Report, chan<- bool)) {

func manageProcceses(processChan chan<- []*lsof.Process) {
for {
time.Sleep(500 * time.Duration(time.Millisecond))
time.Sleep(200 * time.Duration(time.Millisecond))
userPids, err := lsof.GetUserProcessList()
if err != nil {
log.Fatal(err)
Expand All @@ -150,7 +151,7 @@ func manageProcceses(processChan chan<- []*lsof.Process) {

func manageConnections(connectionsChan chan<- []*lsof.ConnectionDetails) {
for {
time.Sleep(500 * time.Duration(time.Millisecond))
time.Sleep(200 * time.Duration(time.Millisecond))
connDeets, err := lsof.MonitorUserConnections()
if err != nil {
log.Fatal(err)
Expand All @@ -162,7 +163,7 @@ func manageConnections(connectionsChan chan<- []*lsof.ConnectionDetails) {
func reportSats(c *connectionsState, reportChan chan<- *stats.Report) {
for {
report := stats.NewReport()
time.Sleep(500 * time.Duration(time.Millisecond))
time.Sleep(200 * time.Duration(time.Millisecond))
for pid, sockIdToConnDeets := range c.connDeets {
processName := c.processes[pid].Name
for socketId, connDeets := range sockIdToConnDeets {
Expand Down
90 changes: 87 additions & 3 deletions stats/analysis.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package stats

import (
"fmt"
"net"
"github.com/blasrodri/frown/dns"
"github.com/blasrodri/frown/lsof"
"strconv"
)

type ConnectionReport struct {
Expand All @@ -13,11 +16,11 @@ type ConnectionReport struct {
}

func AnalyzeSecurity(connDeet *lsof.ConnectionDetails) (*ConnectionReport, error) {
secLevel, additionalInfo := getSecurityLevel(connDeet)
domainName, err := dns.DomainName(&connDeet.RemoteAddrIP)
if err != nil {
return nil, err
}
secLevel, additionalInfo := getSecurityLevel(domainName, connDeet)
return &ConnectionReport{
SocketId: connDeet.SocketId,
DomainName: domainName,
Expand All @@ -26,6 +29,87 @@ func AnalyzeSecurity(connDeet *lsof.ConnectionDetails) (*ConnectionReport, error
}, err
}

func getSecurityLevel(connDeet *lsof.ConnectionDetails) (int, string) {
return 0, ""
func getSecurityLevel(domainName string, connDeet *lsof.ConnectionDetails) (int, string) {
worseScore := 0
message := ""
portScore, msg := portHeuristic(connDeet)
if portScore > worseScore {
worseScore = portScore
message = msg
}

hostScore, msg := hostHeuristic(domainName, connDeet)
if hostScore > worseScore {
worseScore = hostScore
message = msg
}
return worseScore, message
}

func hostHeuristic(domainName string, connDeet *lsof.ConnectionDetails) (int, string) {
if _, ok := dns.KnownDomains[domainName]; ok {
return 0, ""
}

if connDeet.LocalAddrIP.Equal(net.ParseIP("0.0.0.0")) {
return 2, fmt.Sprintf("Service is listening on all interfaces!")
}

if connDeet.RemoteAddrIP.Equal(net.ParseIP("0.0.0.0")) {
return 2, fmt.Sprintf("Service is listening on all interfaces!")
}

return 1, fmt.Sprintf("Unknown host: %s", connDeet.RemoteAddrIP)
}

func portHeuristic(connDeet *lsof.ConnectionDetails) (int, string) {
localPort, err := strconv.Atoi(connDeet.LocalAddrPort)
remotePort, err := strconv.Atoi(connDeet.RemoteAddrPort)
if err != nil {
return 0, ""
}
unEncryptedRemotePorts := []int{80}
safeRemotePorts := []int{443}
databasePorts := []int{3306, 5432}
sshPort := []int{22}

if contains(sshPort, localPort) {
return 3, "SSH Server open, and being accesed"
}

if contains(sshPort, remotePort) {
return 3, "SSH Server open, and being accesed"
}

if contains(safeRemotePorts, remotePort) {
return 0, ""
}

if contains(databasePorts, remotePort) {
return 0, ""
}

if contains(databasePorts, localPort) {
return 0, ""
}

if contains(unEncryptedRemotePorts, remotePort) {
return 2, "Connection not encrypted"
}


if contains(sshPort, remotePort) {
return 0, ""
}

return 1, fmt.Sprintf("Unknown ports: Remote %d, Local %d", remotePort, localPort)
}

func contains(arr []int, el int) bool {
for _, value := range arr {
if el == value {
return true
}
}
return false
}
59 changes: 51 additions & 8 deletions ui/ui.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package ui

import (
"log"
"strconv"
"github.com/blasrodri/frown/stats"
ui "github.com/gizak/termui/v3"
"github.com/gizak/termui/v3/widgets"
"log"
"sort"
"strings"
"strconv"
)

func TerminalUI(reportChan <- chan *stats.Report, closeChan chan <- bool) {
type UIConfig struct {
FilterSecurityLevel int
}

func TerminalUI(config *UIConfig, reportChan <-chan *stats.Report, closeChan chan<- bool) {
if err := ui.Init(); err != nil {
log.Fatalf("failed to initialize termui: %v", err)
}
Expand All @@ -20,20 +26,57 @@ func TerminalUI(reportChan <- chan *stats.Report, closeChan chan <- bool) {
title.SetRect(0, 0, termWidth, 5)
title.Border = false
table1 := widgets.NewTable()

for {
report := <- reportChan
table1.Rows = make([][]string, 0)
report := <-reportChan
rows := make([][]string, 0)
for processName, mapConnRep := range report.ProcessInfo {
for pid, socketIdMapConnReport := range mapConnRep {
for _, connReport := range socketIdMapConnReport {
table1.Rows = append(table1.Rows, []string{processName, strconv.Itoa(pid), connReport.DomainName})
if connReport.SecurityLevel >= config.FilterSecurityLevel {
rows = append(rows, []string{processName, strconv.Itoa(pid), connReport.DomainName, connReport.AdditionalInfo, strconv.Itoa(connReport.SecurityLevel)})
}
}
}
}
table1.TextStyle = ui.NewStyle(ui.ColorWhite)
table1.Rows = make([][]string, len(rows) + 1)
table1.Rows[0] = []string{"Process", "PID", "Domain", "Additional Info"}

sort.Slice(rows, func(i, j int) bool {
switch(strings.Compare(rows[i][0], rows[j][0])) {
case -1:
return true
case 0:
return strings.Compare(rows[i][2], rows[j][2]) == -1
default:
return false
}
})

for i, _ := range rows {
idx := 1 + i
table1.RowStyles[idx] = ui.NewStyle(ui.ColorWhite)
}

for i, row := range rows {
idx := 1 + i
table1.Rows[idx] = row[:len(row) - 1]
secLevel, _ := strconv.Atoi(row[len(row) - 1])
switch secLevel {
case 0:
case 1:
case 2:
table1.RowStyles[idx] = ui.NewStyle(ui.ColorWhite, ui.ColorYellow)
continue
case 3:
table1.RowStyles[idx] = ui.NewStyle(ui.ColorWhite, ui.ColorRed, ui.ModifierBold)
continue
default:
}
}
table1.SetRect(0, 3, termWidth, termHeight)
ui.Render(title, table1)
go func (){
go func() {
uiEvents := ui.PollEvents()
for {
e := <-uiEvents
Expand Down

0 comments on commit c50330f

Please sign in to comment.