Skip to content

Commit

Permalink
add censys check
Browse files Browse the repository at this point in the history
  • Loading branch information
yvesago committed Jun 5, 2024
1 parent 5318ede commit 4cb7a8c
Show file tree
Hide file tree
Showing 3 changed files with 359 additions and 0 deletions.
128 changes: 128 additions & 0 deletions check/censys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package check

import (
"encoding/base64"
"encoding/json"
"fmt"
"net"
"sort"
"strings"

"github.com/jreisinger/checkip"
)

type censys struct {
Result result `json:"result"`
}

type result struct {
Data censysData `json:"services"`
OS operatingSystem `json:"operating_system"`
}

type operatingSystem struct {
Product string `json:"product"`
Vendor string `json:"vendor"`
Version string `json:"version"`
Edition string `json:"edition"`
}

type censysData []struct {
Port int `json:"port"`
Transport string `json:"transport_protocol"` // tcp, udp
ServiceName string `json:"service_name"`
ExtendedServiceName string `json:"extended_service_name"`
}

var censysUrl = "https://search.censys.io/api/v2"

func basicAuth(username, password string) string {
auth := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}

// Censys gets generic information from search.censys.io.
func Censys(ipaddr net.IP) (checkip.Result, error) {
result := checkip.Result{
Name: "censys.io",
Type: checkip.TypeInfoSec,
}

apiKey, err := getConfigValue("CENSYS_KEY")
if err != nil {
return result, newCheckError(err)
}
if apiKey == "" {
return result, nil
}

apiSec, err := getConfigValue("CENSYS_SEC")
if err != nil {
return result, newCheckError(err)
}
if apiSec == "" {
return result, nil
}

headers := map[string]string{
"Authorization": "Basic " + basicAuth(apiKey, apiSec),
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
}

var censys censys
apiURL := fmt.Sprintf("%s/hosts/%s", censysUrl, ipaddr)
if err := defaultHttpClient.GetJson(apiURL, headers, map[string]string{}, &censys); err != nil {
return result, newCheckError(err)
}
result.Info = censys

for _, d := range censys.Result.Data {
port := d.Port
if port != 80 && port != 443 && port != 53 { // undecidable ports
result.Malicious = true
}
}

return result, nil
}

type byPortC censysData

func (x byPortC) Len() int { return len(x) }
func (x byPortC) Less(i, j int) bool { return x[i].Port < x[j].Port }
func (x byPortC) Swap(i, j int) { x[i], x[j] = x[j], x[i] }

// Info returns interesting information from the check.
func (c censys) Summary() string {
var portInfo []string
var os string
if c.Result.OS.Product != "" {
os = c.Result.OS.Product
}
var osvendor string
if c.Result.OS.Vendor != "" {
osvendor = c.Result.OS.Vendor
}

service := make(map[string]int)
sort.Sort(byPortC(c.Result.Data))
for _, d := range c.Result.Data {
port := d.Port

sport := fmt.Sprintf("%s/%d", strings.ToLower(d.Transport), port)
service[sport]++

if service[sport] > 1 {
continue
}

portInfo = append(portInfo, fmt.Sprintf("%s (%s)", sport, strings.ToLower(d.ServiceName)))
}

return fmt.Sprintf("OS: %s, open: %s", strings.Join(nonEmpty(os, osvendor), ", "), strings.Join(portInfo, ", "))
}

func (c censys) Json() ([]byte, error) {
return json.Marshal(c)
}
63 changes: 63 additions & 0 deletions check/censys_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package check

import (
"net"
"net/http"
"testing"

"github.com/jreisinger/checkip"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCensys(t *testing.T) {

apiKey, err := getConfigValue("CENSYS_KEY")
if err != nil || apiKey == "" {
return
}
apiSec, err := getConfigValue("CENSYS_SEC")
if err != nil || apiSec == "" {
return
}

t.Run("given valid response then result and no error is returned", func(t *testing.T) {
handlerFn := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
rw.Write(loadResponse(t, "censys_response.json"))
})

testUrl := setMockHttpClient(t, handlerFn)
setCensysUrl(t, testUrl)

result, err := Censys(net.ParseIP("118.25.6.39"))
require.NoError(t, err)
assert.Equal(t, "censys.io", result.Name)
assert.Equal(t, checkip.TypeInfoSec, result.Type)
assert.Equal(t, true, result.Malicious)
assert.Equal(t, "OS: RB760iGS, MikroTik, open: udp/161 (snmp), tcp/2000 (mikrotik_bw), tcp/51922 (ssh)", result.Info.Summary())
})


t.Run("given non 2xx response then error is returned", func(t *testing.T) {
handlerFn := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusInternalServerError)
})

testUrl := setMockHttpClient(t, handlerFn)
setCensysUrl(t, testUrl)

_, err := Censys(net.ParseIP("118.25.6.39"))
require.Error(t, err)
})
}

// --- test helpers ---

func setCensysUrl(t *testing.T, testUrl string) {
url := censysUrl
censysUrl = testUrl
t.Cleanup(func() {
censysUrl = url
})
}
168 changes: 168 additions & 0 deletions check/testdata/censys_response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
{
"code": 200,
"result": {
"autonomous_system": {
"asn": 0,
"bgp_prefix": "10.20.80.0/21",
"country_code": "BR",
"description": "SERVICOS DE TELECOM",
"name": "SERVICOS DE TELECOM"
},
"autonomous_system_updated_at": "2024-04-19T11:18:35.426760638Z",
"dns": {
"reverse_dns": {
"names": [
"exemple.org"
],
"resolved_at": "2024-05-21T08:16:15.901097328Z"
}
},
"ip": "10.20.84.2",
"labels": [
"network-administration",
"network.device",
"remote-access"
],
"last_updated_at": "2024-06-04T04:19:53.614Z",
"location": {
"city": "Fortaleza",
"continent": "South America",
"coordinates": {
"latitude": -3.70000,
"longitude": -38.00000
},
"country": "Brazil",
"country_code": "BR",
"postal_code": "60000-000",
"province": "Ceará",
"timezone": "America/Fortaleza"
},
"location_updated_at": "2024-05-29T11:18:35.426738659Z",
"operating_system": {
"other": {
"device": "Router",
"family": "RouterOS"
},
"part": "o",
"product": "RB760iGS",
"uniform_resource_identifier": "cpe:2.3:o:mikrotik:rb760igs:*:*:*:*:*:*:*:*",
"vendor": "MikroTik"
},
"services": [
{
"_decoded": "snmp",
"discovery_method": "PREDICTIVE_METHOD_7",
"extended_service_name": "SNMP",
"labels": [
"network-administration"
],
"observed_at": "2024-06-03T21:51:32.698760154Z",
"perspective_id": "PERSPECTIVE_HE",
"port": 161,
"service_name": "SNMP",
"snmp": {},
"software": [
{
"other": {
"device": "Router",
"family": "RouterOS"
},
"part": "o",
"product": "RB760iGS",
"source": "OSI_APPLICATION_LAYER",
"uniform_resource_identifier": "cpe:2.3:o:mikrotik:rb760igs:*:*:*:*:*:*:*:*",
"vendor": "MikroTik"
}
],
"source_ip": "192.168.125.204",
"transport_protocol": "UDP",
"truncated": false
},
{
"_decoded": "banner_grab",
"_encoding": {
"banner": "DISPLAY_UTF8",
"banner_hex": "DISPLAY_HEX"
},
"banner": "\u0001\u0000\u0000\u0000",
"banner_hashes": [
""
],
"banner_hex": "01000000",
"discovery_method": "IPV4_WALK_FULL_PRIORITY_1",
"extended_service_name": "MIKROTIK_BW",
"observed_at": "2024-06-04T03:58:59.168261755Z",
"perspective_id": "PERSPECTIVE_TATA",
"port": 2000,
"service_name": "MIKROTIK_BW",
"software": [
{
"part": "o",
"product": "Linux",
"source": "OSI_TRANSPORT_LAYER",
"uniform_resource_identifier": "cpe:2.3:o:*:linux:*:*:*:*:*:*:*:*"
}
],
"source_ip": "192.168.138.113",
"transport_fingerprint": {
"id": 15,
"os": "device25",
"raw": "14480,64,true,MSTNW,1460,false,false"
},
"transport_protocol": "TCP",
"truncated": false
},
{
"_decoded": "ssh",
"_encoding": {
"banner": "DISPLAY_UTF8",
"banner_hex": "DISPLAY_HEX"
},
"banner": "SSH-2.0-ROSSSH",
"banner_hashes": [
""
],
"banner_hex": "",
"discovery_method": "PREDICTIVE_METHOD_20",
"extended_service_name": "SSH",
"labels": [
"remote-access",
"network.device"
],
"observed_at": "2024-06-04T04:14:51.357372990Z",
"perspective_id": "PERSPECTIVE_TATA",
"port": 51922,
"service_name": "SSH",
"software": [
{
"other": {
"device": "Router",
"family": "RouterOS"
},
"part": "o",
"product": "RouterOS",
"source": "OSI_APPLICATION_LAYER",
"uniform_resource_identifier": "cpe:2.3:o:mikrotik:routeros:*:*:*:*:*:*:*:*",
"vendor": "MikroTik"
}
],
"source_ip": "192.168.138.48",
"ssh": {},
"transport_protocol": "TCP",
"truncated": false
}
],
"whois": {
"network": {
"cidrs": [
"10.20.64.0/18"
],
"created": "2007-06-13T00:00:00Z",
"handle": "PASxxx",
"name": "SERVICOS DE TELECOM",
"updated": "2024-01-20T00:00:00Z"
}
}
},
"status": "OK"
}

0 comments on commit 4cb7a8c

Please sign in to comment.