package main import ( "crypto/sha256" "crypto/subtle" "database/sql" "encoding/json" "fmt" "html/template" "log" "net/http" "os" "strings" _ "github.com/go-sql-driver/mysql" _ "github.com/mattn/go-sqlite3" ) var port = "9000" var ( username string = "meer" password string = "Satori" ) type BannerStore interface { QueryHTTPBanners(listeningOnly, hasTitle, interesting bool, search string) ([]Host, error) QuerySSHBanners(search string, listeningOnly bool) ([]SSHHost, error) QueryTelnetBanners(search string, listeningOnly, hasBanner bool) ([]TelnetHost, error) QueryHTTPBannerByIP(ip string) (*HostDetail, error) } type SQLBannerStore struct { db *sql.DB } type HostDetail struct { Ip, Title, Resp, Headers, Server, ContentType, ContentLength, Redirect, MetaTags, BodySnippet string IsListening bool } type TelnetHost struct { Ip, Firstline string IsListening bool } type SSHHost struct { Ip, Banner string IsListening bool } var db *sql.DB var Store BannerStore type DBConfig struct { DbDriver string `json:"dbdriver"` Username string `json:"username"` Password string `json:"password"` DBName string `json:"dbname"` } type Webconfig struct { Port int `json:"port"` Username string `json:"username"` Password string `json:"password"` } type Config struct { ZmapFile string `json:"zmapfile"` Channelfeeders int `json:"channelfeeders"` DB DBConfig `json:"db"` Web Webconfig `json:"web"` } func loadConfig(path string) (*Config, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() var cfg Config if err := json.NewDecoder(f).Decode(&cfg); err != nil { return nil, err } return &cfg, nil } type Host struct { Ip, Title, Resp, Server, ContentType, ContentLength, Redirect string IsListening bool } func sqlInitMySQLFrontend(dsn string) (*SQLBannerStore, error) { db, err := sql.Open("mysql", dsn) if err != nil { return nil, err } return &SQLBannerStore{db: db}, nil } func sqlInitSQLiteFrontend(dbname string) (*SQLBannerStore, error) { db, err := sql.Open("sqlite3", dbname) if err != nil { return nil, err } return &SQLBannerStore{db: db}, nil } func (s *SQLBannerStore) QueryHTTPBanners(listeningOnly, hasTitle, interesting bool, search string) ([]Host, error) { q := `SELECT Ip, IsListening, httptitle, Httpresp, ServerHeader, ContentType, ContentLength, Redirect FROM httpbanners WHERE 1=1` args := []interface{}{} if listeningOnly { q += " AND IsListening=1" } if hasTitle { q += " AND httptitle IS NOT NULL AND httptitle<>''" } if search != "" { q += " AND (Ip LIKE ? OR httptitle LIKE ? OR ServerHeader LIKE ? OR ContentType LIKE ? OR Redirect LIKE ?)" like := "%" + search + "%" args = append(args, like, like, like, like, like) } if interesting { q += buildExclusionSQL("excluded_titles.txt") } q += " ORDER BY Ip" rows, err := s.db.Query(q, args...) if err != nil { return nil, err } defer rows.Close() var hosts []Host for rows.Next() { var h Host if err := rows.Scan(&h.Ip, &h.IsListening, &h.Title, &h.Resp, &h.Server, &h.ContentType, &h.ContentLength, &h.Redirect); err != nil { continue } hosts = append(hosts, h) } return hosts, nil } func (s *SQLBannerStore) QueryTelnetBanners(search string, listeningOnly, hasBanner bool) ([]TelnetHost, error) { q := "SELECT Ip, IsListening, firstline FROM telnetbanners WHERE 1=1" args := []interface{}{} if listeningOnly { q += " AND IsListening=1" } if hasBanner { q += " AND firstline IS NOT NULL AND firstline<>''" } if search != "" { q += " AND (Ip LIKE ? OR firstline LIKE ?)" like := "%" + search + "%" args = append(args, like, like) } q += " ORDER BY Ip" rows, err := s.db.Query(q, args...) if err != nil { return nil, err } defer rows.Close() var hosts []TelnetHost for rows.Next() { var h TelnetHost if err := rows.Scan(&h.Ip, &h.IsListening, &h.Firstline); err != nil { continue } hosts = append(hosts, h) } return hosts, nil } func (s *SQLBannerStore) QueryHTTPBannerByIP(ip string) (*HostDetail, error) { row := s.db.QueryRow(`SELECT Ip, httptitle, Httpresp, Headers, ServerHeader, ContentType, ContentLength, Redirect, MetaTags, BodySnippet, IsListening FROM httpbanners WHERE Ip=?`, ip) var h HostDetail if err := row.Scan(&h.Ip, &h.Title, &h.Resp, &h.Headers, &h.Server, &h.ContentType, &h.ContentLength, &h.Redirect, &h.MetaTags, &h.BodySnippet, &h.IsListening); err != nil { return nil, err } return &h, nil } func (s *SQLBannerStore) QuerySSHBanners(search string, listeningOnly bool) ([]SSHHost, error) { q := "SELECT Ip, IsListening, firstline FROM sshbanners WHERE 1=1" args := []interface{}{} if listeningOnly { q += " AND IsListening=1" } if search != "" { q += " AND (Ip LIKE ? OR firstline LIKE ?)" like := "%" + search + "%" args = append(args, like, like) } rows, err := s.db.Query(q, args...) if err != nil { return nil, err } defer rows.Close() var hosts []SSHHost for rows.Next() { var h SSHHost if err := rows.Scan(&h.Ip, &h.IsListening, &h.Banner); err != nil { continue } hosts = append(hosts, h) } return hosts, nil } func main() { cfg, err := loadConfig("config.json") if err != nil { log.Fatal(err) } switch cfg.DB.DbDriver { case "mysql": dsn := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s", cfg.DB.Username, cfg.DB.Password, cfg.DB.DBName) Store, err = sqlInitMySQLFrontend(dsn) case "sqlite": Store, err = sqlInitSQLiteFrontend(cfg.DB.DBName) default: log.Fatalf("Unsupported driver: %s", cfg.DB.DbDriver) } if err != nil { log.Fatal(err) } fmt.Println("DB opened") http.HandleFunc("/", home) http.HandleFunc("/http", listHTTP) http.HandleFunc("/telnet", listTelnet) http.HandleFunc("/ssh", listSSH) http.HandleFunc("/ssh/", sshDetail) http.HandleFunc("/http/", httpDetail) http.HandleFunc("/telnet/", telnetDetail) http.HandleFunc("/admin", basicAuth(adminPanel, cfg.Web.Username, cfg.Web.Password)) fmt.Printf("Frontend running at http://localhost:%d\n", cfg.Web.Port) http.ListenAndServe(fmt.Sprintf(":%d", cfg.Web.Port), nil) } func home(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, `
for now, only telnet and http and SSH are available, more will be added soon
`) } func listHTTP(w http.ResponseWriter, r *http.Request) { listening := r.URL.Query().Get("listening") == "1" hasTitle := r.URL.Query().Get("title") == "1" interesting := r.URL.Query().Get("interesting") == "1" search := r.URL.Query().Get("q") // Ask the BannerStore for results hosts, err := Store.QueryHTTPBanners(listening, hasTitle, interesting, search) if err != nil { http.Error(w, "DB error: "+err.Error(), 500) return } tmpl := template.Must(template.New("http").Parse(`| IP | Listening | Title | Server | Content-Type | Response | Length | Redirect |
|---|---|---|---|---|---|---|---|
| {{.Ip}} | {{.IsListening}} | {{.Title}} | {{.Server}} | {{.ContentType}} | {{.Resp}} | {{.ContentLength}} | {{.Redirect}} |
Try adjusting your filters or search terms.
| IP | Listening | Title |
|---|---|---|
| {{.Ip}} | {{.IsListening}} | {{.Banner}} |
Try adjusting your filters or search terms.
dont worry, we will eventually update our database for your desired results.
Title: {{.Title}}
Response: {{.Resp}}
Server: {{.Server}}
Content-Type: {{.ContentType}}
Content-Length: {{.ContentLength}}
Redirect: {{.Redirect}}
{{ if .MetaTags}}{{.MetaTags}}
{{ end }}
{{.Headers}}
{{.BodySnippet}}
`))
tmpl.Execute(w, map[string]interface{}{
"Ip": h.Ip,
"Title": h.Title,
"Resp": h.Resp,
"Headers": h.Headers,
"Server": h.Server,
"ContentType": h.ContentType,
"ContentLength": h.ContentLength,
"Redirect": h.Redirect,
"MetaTags": h.MetaTags,
"BodySnippet": h.BodySnippet,
})
}
func listTelnet(w http.ResponseWriter, r *http.Request) {
listening := r.URL.Query().Get("listening") == "1"
banner := r.URL.Query().Get("banner") == "1"
search := r.URL.Query().Get("q")
hosts, err := Store.QueryTelnetBanners(search, listening, banner)
if err != nil {
http.Error(w, "DB error: "+err.Error(), 500)
return
}
tmpl := template.Must(template.New("telnet").Parse(`
| IP | Listening | Firstline |
|---|---|---|
| {{.Ip}} | {{.IsListening}} | {{.Firstline}} |
{{.Banner}}
`))
tmpl.Execute(w, map[string]interface{}{
"Ip": ipAddr,
"Banner": banner,
})
}
const css = ``
// Admin panel handler
func adminPanel(w http.ResponseWriter, r *http.Request) {
fmt.Printf("admin login from %s, X-real-IP header: %s\n", r.RemoteAddr, r.Header.Get("X-Real-IP"))
if r.Method == http.MethodPost {
// Delete IP
if ip := r.FormValue("delete_ip"); ip != "" {
_, err := db.Exec("DELETE FROM httpbanners WHERE Ip = ?", ip)
fmt.Println("[admin panel] deleting IP: ", ip)
if err != nil {
http.Error(w, "Error deleting IP: "+err.Error(), http.StatusInternalServerError)
return
}
}
// Add excluded title
if title := r.FormValue("add_title"); title != "" {
f, err := os.OpenFile("excluded_titles.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
http.Error(w, "Error writing title: "+err.Error(), http.StatusInternalServerError)
return
}
defer f.Close()
f.WriteString(title + "\n")
}
if remove := r.FormValue("remove_title"); remove != "" {
data, err := os.ReadFile("excluded_titles.txt")
if err == nil {
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
var newLines []string
for _, l := range lines {
if strings.TrimSpace(l) != remove {
newLines = append(newLines, l)
}
}
os.WriteFile("excluded_titles.txt", []byte(strings.Join(newLines, "\n")+"\n"), 0644)
}
}
}
var titles []string
if data, err := os.ReadFile("excluded_titles.txt"); err == nil {
titles = strings.Split(strings.TrimSpace(string(data)), "\n")
}
tmpl := template.Must(template.New("admin").Parse(`