Compare commits

..

3 Commits

Author SHA1 Message Date
Dan Ballard 52fab4e2b5 cleaning up, reporting 2016-06-01 07:15:44 -07:00
Dan Ballard e1760189a0 attempts to get v3 fingerprint 2016-05-29 20:55:09 -07:00
Dan Ballard aa8f8aff57 initial import of pgp libs 2016-05-27 08:04:10 -07:00
27 changed files with 276 additions and 680 deletions

View File

@ -58,7 +58,7 @@ This [should not be news](http://arstechnica.com/security/2016/02/default-settin
* Determine client IP addresses if you are co-hosting a clearnet site. * Determine client IP addresses if you are co-hosting a clearnet site.
* Determine your IP address if your setup allows. * Determine your IP address if your setup allows.
* Determine other sites you are co-hosting. * Determine other sites you are co-hosting.
* Determine how active your site is. * Determine how active your site it.
* Find secret or hidden areas of your site * Find secret or hidden areas of your site
* and much, much more. * and much, much more.

View File

@ -1,34 +1,15 @@
package config package config
import ( import ()
"log"
"time"
)
type OnionscanConfig struct { type OnionscanConfig struct {
TorProxyAddress string TorProxyAddress string
DirectoryDepth int DirectoryDepth int
Fingerprint bool
Timeout time.Duration
Verbose bool
} }
func Configure(torProxyAddress string, directoryDepth int, fingerprint bool, timeout int, verbose bool) *OnionscanConfig { func Configure(torProxyAddress string, directoryDepth int) *OnionscanConfig {
onionScan := new(OnionscanConfig) onionScan := new(OnionscanConfig)
onionScan.TorProxyAddress = torProxyAddress onionScan.TorProxyAddress = torProxyAddress
onionScan.DirectoryDepth = directoryDepth onionScan.DirectoryDepth = directoryDepth
onionScan.Fingerprint = fingerprint
onionScan.Timeout = time.Duration(time.Second * time.Duration(timeout))
onionScan.Verbose = verbose
return onionScan return onionScan
} }
func (os *OnionscanConfig) LogInfo(message string) {
if os.Verbose {
log.Printf("INFO: %v", message)
}
}
func (os *OnionscanConfig) LogError(err error) {
log.Printf("ERROR: %v", err)
}

79
main.go
View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"errors"
"flag" "flag"
"fmt" "fmt"
"github.com/s-rah/onionscan/config" "github.com/s-rah/onionscan/config"
@ -9,96 +8,52 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"strings"
) )
func main() { func main() {
flag.Usage = func() { flag.Usage = func() {
fmt.Printf("Usage of %s:\n", os.Args[0]) fmt.Printf("Usage of %s:\n", os.Args[0])
fmt.Printf(" onionscan [flags] hiddenservice | onionscan [flags] --list list\n") fmt.Printf(" onionscan [flags] hiddenservice\n")
flag.PrintDefaults() flag.PrintDefaults()
} }
torProxyAddress := flag.String("torProxyAddress", "127.0.0.1:9050", "the address of the tor proxy to use") torProxyAddress := flag.String("torProxyAddress", "127.0.0.1:9050", "the address of the tor proxy to use")
simpleReport := flag.Bool("simpleReport", true, "print out a simple report detailing what is wrong and how to fix it, true by default") simpleReport := flag.Bool("simpleReport", true, "print out a simple report detailing what is wrong and how to fix it, true by default")
reportFile := flag.String("reportFile", "", "the file destination path for report file - if given, the prefix of the file will be the scanned onion service. If not given, the report will be written to stdout") reportFile := flag.String("reportFile", "", "the file destination path for report file")
jsonReport := flag.Bool("jsonReport", false, "print out a json report providing a detailed report of the scan.") jsonReport := flag.Bool("jsonReport", false, "print out a json report providing a detailed report of the scan.")
verbose := flag.Bool("verbose", false, "print out a verbose log output of the scan") verbose := flag.Bool("verbose", false, "print out a verbose log output of the scan")
directoryDepth := flag.Int("depth", 100, "depth of directory scan recursion (default: 100)") directoryDepth := flag.Int("depth", 100, "depth of directory scan recursion (default: 100)")
fingerprint := flag.Bool("fingerprint", true, "true disables some deeper scans e.g. directory probing with the aim of just getting a fingerprint of the service.")
list := flag.String("list", "", "If provided OnionScan will attempt to read from the given list, rather than the provided hidden service")
timeout := flag.Int("timeout", 120, "read timeout for connecting to onion services")
batch := flag.Int("batch", 10, "number of onions to scan concurrently")
flag.Parse() flag.Parse()
if len(flag.Args()) != 1 && *list == "" { if len(flag.Args()) != 1 {
flag.Usage() flag.Usage()
os.Exit(1) os.Exit(1)
} }
if !*simpleReport && !*jsonReport { hiddenService := flag.Args()[0]
log.Fatalf("You must set one of --simpleReport or --jsonReport")
}
onionsToScan := []string{} log.Printf("Starting Scan of %s\n", hiddenService)
if *list == "" {
onionsToScan = append(onionsToScan, flag.Args()[0])
log.Printf("Starting Scan of %s\n", flag.Args()[0])
} else {
content, err := ioutil.ReadFile(*list)
if err != nil {
log.Fatalf("Could not read onion file %s\n", *list)
}
onions := strings.Split(string(content), "\n")
for _, onion := range onions[0 : len(onions)-1] {
onionsToScan = append(onionsToScan, onion)
}
log.Printf("Starting Scan of %d onion services\n", len(onionsToScan))
}
log.Printf("This might take a few minutes..\n\n") log.Printf("This might take a few minutes..\n\n")
if !*verbose {
log.SetOutput(ioutil.Discard)
}
onionScan := new(OnionScan) onionScan := new(OnionScan)
onionScan.Config = config.Configure(*torProxyAddress, *directoryDepth, *fingerprint, *timeout, *verbose) onionScan.Config = config.Configure(*torProxyAddress, *directoryDepth)
scanReport, err := onionScan.Scan(hiddenService)
reports := make(chan *report.OnionScanReport) if err != nil {
log.Fatalf("Error running scanner: %s", err)
count := 0
if *batch > len(onionsToScan) {
*batch = len(onionsToScan)
} }
// Run an initial batch of 100 requests (or less...) if *jsonReport {
for count < *batch { report.GenerateJsonReport(*reportFile, scanReport)
go onionScan.Scan(onionsToScan[count], reports)
count++
} }
received := 0 if *simpleReport {
for received < len(onionsToScan) { report.GenerateSimpleReport(*reportFile, scanReport)
scanReport := <-reports
// After the initial batch, it's one in one out to prevent proxy overload.
if count < len(onionsToScan) {
go onionScan.Scan(onionsToScan[count], reports)
count++
}
received++
if scanReport.TimedOut {
onionScan.Config.LogError(errors.New(scanReport.HiddenService + " timed out"))
}
file := *reportFile
if file != "" {
file = scanReport.HiddenService + "." + *reportFile
}
if *jsonReport {
report.GenerateJsonReport(file, scanReport)
} else if *simpleReport {
report.GenerateSimpleReport(file, scanReport)
}
} }
} }

View File

@ -1,72 +1,20 @@
package main package main
import ( import (
"errors"
"fmt"
"github.com/s-rah/onionscan/config" "github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/protocol" "github.com/s-rah/onionscan/protocol"
"github.com/s-rah/onionscan/report" "github.com/s-rah/onionscan/report"
"github.com/s-rah/onionscan/utils" "github.com/s-rah/onionscan/utils"
"strings" "strings"
"time"
) )
type OnionScan struct { type OnionScan struct {
Config *config.OnionscanConfig Config *config.OnionscanConfig
} }
func (os *OnionScan) PerformNextAction(report *report.OnionScanReport) { func (os *OnionScan) Scan(hiddenService string) (*report.OnionScanReport, error) {
switch report.NextAction {
case "web":
wps := new(protocol.HTTPProtocolScanner)
wps.ScanProtocol(report.HiddenService, os.Config, report)
report.NextAction = "tls"
case "tls":
tps := new(protocol.TLSProtocolScanner)
tps.ScanProtocol(report.HiddenService, os.Config, report)
report.NextAction = "ssh"
case "ssh":
sps := new(protocol.SSHProtocolScanner)
sps.ScanProtocol(report.HiddenService, os.Config, report)
report.NextAction = "irc"
case "irc":
ips := new(protocol.IRCProtocolScanner)
ips.ScanProtocol(report.HiddenService, os.Config, report)
report.NextAction = "ricochet"
case "ricochet":
rps := new(protocol.RicochetProtocolScanner)
rps.ScanProtocol(report.HiddenService, os.Config, report)
report.NextAction = "ftp"
case "ftp":
fps := new(protocol.FTPProtocolScanner)
fps.ScanProtocol(report.HiddenService, os.Config, report)
report.NextAction = "smtp"
case "smtp":
smps := new(protocol.SMTPProtocolScanner)
smps.ScanProtocol(report.HiddenService, os.Config, report)
report.NextAction = "mongodb"
case "mongodb":
mdbps := new(protocol.MongoDBProtocolScanner)
mdbps.ScanProtocol(report.HiddenService, os.Config, report)
report.NextAction = "vnc"
case "vnc":
vncps := new(protocol.VNCProtocolScanner)
vncps.ScanProtocol(report.HiddenService, os.Config, report)
report.NextAction = "xmpp"
case "xmpp":
xmppps := new(protocol.XMPPProtocolScanner)
xmppps.ScanProtocol(report.HiddenService, os.Config, report)
report.NextAction = "bitcoin"
case "bitcoin":
bps := new(protocol.BitcoinProtocolScanner)
bps.ScanProtocol(report.HiddenService, os.Config, report)
report.NextAction = "none"
case "none":
return
default:
report.NextAction = "web"
}
}
func (os *OnionScan) Scan(hiddenService string, out chan *report.OnionScanReport) {
// Remove Extra Prefix // Remove Extra Prefix
hiddenService = utils.WithoutProtocol(hiddenService) hiddenService = utils.WithoutProtocol(hiddenService)
@ -77,13 +25,42 @@ func (os *OnionScan) Scan(hiddenService string, out chan *report.OnionScanReport
report := report.NewOnionScanReport(hiddenService) report := report.NewOnionScanReport(hiddenService)
for report.NextAction != "none" { // HTTP
os.PerformNextAction(report) hps := new(protocol.HTTPProtocolScanner)
if time.Now().Sub(report.DateScanned).Seconds() > os.Config.Timeout.Seconds() { hps.ScanProtocol(hiddenService, os.Config, report)
report.TimedOut = true
report.NextAction = "none" // SSH
} sps := new(protocol.SSHProtocolScanner)
sps.ScanProtocol(hiddenService, os.Config, report)
// Ricochet
rps := new(protocol.RicochetProtocolScanner)
rps.ScanProtocol(hiddenService, os.Config, report)
// Bitcoin
bps := new(protocol.BitcoinProtocolScanner)
bps.ScanProtocol(hiddenService, os.Config, report)
//IRC
ips := new(protocol.IRCProtocolScanner)
ips.ScanProtocol(hiddenService, os.Config, report)
//FTP
fps := new(protocol.FTPProtocolScanner)
fps.ScanProtocol(hiddenService, os.Config, report)
//SMTP
smps := new(protocol.SMTPProtocolScanner)
smps.ScanProtocol(hiddenService, os.Config, report)
//MongoDb
mdbps := new(protocol.MongoDBProtocolScanner)
mdbps.ScanProtocol(hiddenService, os.Config, report)
if !report.WebDetected && !report.SSHDetected && !report.RicochetDetected && !report.BitcoinDetected && !report.IRCDetected && !report.FTPDetected && !report.SMTPDetected && !report.MongoDBDetected {
fmt.Printf("Unable to connect to this Tor Hidden Service on any known protocol.\n")
return nil, errors.New("Unable to connect to this Tor Hidden Service on any known protocol.")
} }
out <- report return report, nil
} }

View File

@ -1,28 +1,26 @@
package protocol package protocol
import ( import (
"fmt"
"github.com/s-rah/onionscan/config" "github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report" "github.com/s-rah/onionscan/report"
"github.com/s-rah/onionscan/utils" "h12.me/socks"
"log"
) )
type BitcoinProtocolScanner struct { type BitcoinProtocolScanner struct {
} }
func (rps *BitcoinProtocolScanner) ScanProtocol(hiddenService string, osc *config.OnionscanConfig, report *report.OnionScanReport) { func (rps *BitcoinProtocolScanner) ScanProtocol(hiddenService string, onionscanConfig *config.OnionscanConfig, report *report.OnionScanReport) {
// Bitcoin // Bitcoin
osc.LogInfo(fmt.Sprintf("Checking %s Bitcoin(8333)\n", hiddenService)) log.Printf("Checking %s Bitcoin(8333)\n", hiddenService)
conn, err := utils.GetNetworkConnection(hiddenService, 8333, osc.TorProxyAddress, osc.Timeout) _, err := socks.DialSocksProxy(socks.SOCKS5, onionscanConfig.TorProxyAddress)("", hiddenService+":8333")
if err != nil { if err != nil {
osc.LogInfo("Failed to connect to service on port 8333\n") log.Printf("Failed to connect to service on port 8333\n")
report.BitcoinDetected = false report.BitcoinDetected = false
} else { } else {
osc.LogInfo("Detected possible Bitcoin instance\n") log.Printf("Detected possible Bitcoin instance\n")
// TODO: Actual Analysis // TODO: Actual Analysis
report.BitcoinDetected = true report.BitcoinDetected = true
} }
if conn != nil {
conn.Close()
}
} }

View File

@ -1,37 +1,25 @@
package protocol package protocol
import ( import (
"bufio"
"crypto/sha1"
"encoding/hex"
"fmt"
"github.com/s-rah/onionscan/config" "github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report" "github.com/s-rah/onionscan/report"
"github.com/s-rah/onionscan/utils" "h12.me/socks"
"log"
) )
type FTPProtocolScanner struct { type FTPProtocolScanner struct {
} }
func (sps *FTPProtocolScanner) ScanProtocol(hiddenService string, osc *config.OnionscanConfig, report *report.OnionScanReport) { func (sps *FTPProtocolScanner) ScanProtocol(hiddenService string, onionscanConfig *config.OnionscanConfig, report *report.OnionScanReport) {
// FTP // FTP
osc.LogInfo(fmt.Sprintf("Checking %s FTP(21)\n", hiddenService)) log.Printf("Checking %s FTP(22)\n", hiddenService)
conn, err := utils.GetNetworkConnection(hiddenService, 21, osc.TorProxyAddress, osc.Timeout) _, err := socks.DialSocksProxy(socks.SOCKS5, onionscanConfig.TorProxyAddress)("", hiddenService+":21")
if err != nil { if err != nil {
osc.LogInfo("Failed to connect to service on port 21\n") log.Printf("Failed to connect to service on port 21\n")
report.FTPDetected = false report.FTPDetected = false
} else { } else {
// TODO FTP Checking
report.FTPDetected = true report.FTPDetected = true
reader := bufio.NewReader(conn)
banner, err := reader.ReadString('\n')
if err == nil {
report.FTPBanner = banner
hash := sha1.Sum([]byte(banner))
report.FTPFingerprint = hex.EncodeToString(hash[:])
osc.LogInfo(fmt.Sprintf("Found FTP Banner: %s (%s)", banner, report.FTPFingerprint))
}
}
if conn != nil {
conn.Close()
} }
} }

View File

@ -1,14 +1,13 @@
package protocol package protocol
import ( import (
"crypto/tls"
"fmt"
"github.com/s-rah/onionscan/config" "github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report" "github.com/s-rah/onionscan/report"
"github.com/s-rah/onionscan/scans" "github.com/s-rah/onionscan/scans"
"github.com/s-rah/onionscan/utils" "github.com/s-rah/onionscan/utils"
"h12.me/socks" "h12.me/socks"
"io/ioutil" "io/ioutil"
"log"
"net/http" "net/http"
"strings" "strings"
) )
@ -25,86 +24,67 @@ var (
"/products", "/products/cat"} "/products", "/products/cat"}
) )
func (hps *HTTPProtocolScanner) ScanProtocol(hiddenService string, osc *config.OnionscanConfig, report *report.OnionScanReport) { func (hps *HTTPProtocolScanner) ScanProtocol(hiddenService string, onionscanConfig *config.OnionscanConfig, report *report.OnionScanReport) {
// HTTP // HTTP
osc.LogInfo(fmt.Sprintf("Checking %s http(80)\n", hiddenService)) log.Printf("Checking %s http(80)\n", hiddenService)
conn, err := utils.GetNetworkConnection(hiddenService, 80, osc.TorProxyAddress, osc.Timeout) _, err := socks.DialSocksProxy(socks.SOCKS5, onionscanConfig.TorProxyAddress)("", hiddenService+":80")
if err != nil { if err != nil {
osc.LogInfo("Failed to connect to service on port 80\n") log.Printf("Failed to connect to service on port 80\n")
report.WebDetected = false report.WebDetected = false
if conn != nil {
conn.Close()
}
} else {
osc.LogInfo("Found potential service on http(80)\n")
report.WebDetected = true
conn.Close()
dialSocksProxy := socks.DialSocksProxy(socks.SOCKS5, osc.TorProxyAddress)
transportConfig := &http.Transport{
Dial: dialSocksProxy,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
hps.Client = &http.Client{
Transport: transportConfig,
}
// FIXME This should probably be moved to it's own file now.
response, err := hps.Client.Get("http://" + hiddenService)
if err == nil {
// Reading all http headers
osc.LogInfo(fmt.Sprintf("HTTP response headers: %s\n", report.ServerVersion))
responseHeaders := response.Header
for key := range responseHeaders {
value := responseHeaders.Get(key)
// normalize by strings.ToUpper(key) to avoid case sensitive checking
report.AddResponseHeader(strings.ToUpper(key), value)
}
report.ServerVersion = responseHeaders.Get("Server")
response.Body.Close()
} else {
osc.LogError(err)
}
// Apache mod-status Check
hps.ScanPage(hiddenService, "/", report, osc, scans.StandardPageScan)
hps.ScanPage(hiddenService, "/server-status", report, osc, scans.ApacheModStatus)
hps.ScanPage(hiddenService, "/private_key", report, osc, scans.PrivateKeyScan)
if osc.Fingerprint == false {
osc.LogInfo("\tScanning Common and Referenced Directories\n")
directories := append(CommonDirectories, report.PageReferencedDirectories...)
utils.RemoveDuplicates(&directories)
for _, directory := range directories {
hps.ScanPage(hiddenService, directory, report, osc, scans.CheckDirectoryListing(osc.DirectoryDepth))
}
}
}
}
func (hps *HTTPProtocolScanner) ScanPage(hiddenService string, page string, report *report.OnionScanReport, osc *config.OnionscanConfig, f func(scans.Scanner, string, int, string, *report.OnionScanReport, *config.OnionscanConfig)) {
err, contents, responseCode := hps.ScrapePage(hiddenService, page)
if err == nil {
f(hps, page, responseCode, string(contents), report, osc)
return return
} else { } else {
osc.LogError(err) log.Printf("Found potential service on http(80)\n")
report.WebDetected = true
dialSocksProxy := socks.DialSocksProxy(socks.SOCKS5, onionscanConfig.TorProxyAddress)
transportConfig := &http.Transport{
Dial: dialSocksProxy,
}
hps.Client = &http.Client{Transport: transportConfig}
// FIXME This should probably be moved to it's own file now.
response, err := hps.Client.Get("http://" + hiddenService)
if err != nil {
log.Printf("Failed to connect to service on port 80\n")
return
}
// Reading all http headers
log.Printf("HTTP response headers: %s\n", report.ServerVersion)
responseHeaders := response.Header
for key := range responseHeaders {
value := responseHeaders.Get(key)
// normalize by strings.ToUpper(key) to avoid case sensitive checking
report.AddResponseHeader(strings.ToUpper(key), value)
log.Printf("\t%s : %s\n", strings.ToUpper(key), value)
}
report.ServerVersion = responseHeaders.Get("Server")
// Apache mod-status Check
hps.ScanPage(hiddenService, "/server-status", report, scans.ApacheModStatus)
hps.ScanPage(hiddenService, "/", report, scans.StandardPageScan)
log.Printf("\tScanning Common and Referenced Directories\n")
directories := append(CommonDirectories, report.PageReferencedDirectories...)
utils.RemoveDuplicates(&directories)
for _, directory := range directories {
hps.ScanPage(hiddenService, directory, report, scans.CheckDirectoryListing(onionscanConfig.DirectoryDepth))
}
} }
log.Printf("\n")
} }
func (hps *HTTPProtocolScanner) ScrapePage(hiddenService string, page string) (error, []byte, int) { func (hps *HTTPProtocolScanner) ScanPage(hiddenService string, page string, report *report.OnionScanReport, f func(scans.Scanner, string, int, string, *report.OnionScanReport)) {
if !strings.Contains(page, utils.WithoutSubdomains(hiddenService)) { if !strings.Contains(page, utils.WithoutSubdomains(hiddenService)) {
if !strings.HasPrefix(page, "/") {
page = "/" + page
}
page = hiddenService + page page = hiddenService + page
} }
response, err := hps.Client.Get("http://" + page) response, err := hps.Client.Get("http://" + page)
if err != nil { if err != nil {
return err, nil, -1 log.Printf("Error connecting to http://%s %s\n", page, err)
return
} }
defer response.Body.Close() defer response.Body.Close()
contents, _ := ioutil.ReadAll(response.Body) contents, _ := ioutil.ReadAll(response.Body)
return nil, contents, response.StatusCode f(hps, page, response.StatusCode, string(contents), report)
} }

View File

@ -1,42 +1,26 @@
package protocol package protocol
import ( import (
"fmt"
"github.com/s-rah/onionscan/config" "github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report" "github.com/s-rah/onionscan/report"
"github.com/s-rah/onionscan/utils" "h12.me/socks"
"log"
) )
type IRCProtocolScanner struct { type IRCProtocolScanner struct {
} }
func (rps *IRCProtocolScanner) ScanProtocol(hiddenService string, osc *config.OnionscanConfig, report *report.OnionScanReport) { func (rps *IRCProtocolScanner) ScanProtocol(hiddenService string, onionscanConfig *config.OnionscanConfig, report *report.OnionScanReport) {
// IRC // IRC
osc.LogInfo(fmt.Sprintf("Checking %s IRC(6667)\n", hiddenService)) log.Printf("Checking %s IRC(6667)\n", hiddenService)
conn, err := utils.GetNetworkConnection(hiddenService, 6667, osc.TorProxyAddress, osc.Timeout) _, err := socks.DialSocksProxy(socks.SOCKS5, onionscanConfig.TorProxyAddress)("", hiddenService+":6667")
if err != nil { if err != nil {
osc.LogInfo("Failed to connect to service on port 6667\n") log.Printf("Failed to connect to service on port 6667\n")
report.IRCDetected = false report.IRCDetected = false
} else { } else {
osc.LogInfo("Detected possible IRC instance\n") log.Printf("Detected possible IRC instance\n")
// TODO: Actual Analysis // TODO: Actual Analysis
report.IRCDetected = true report.IRCDetected = true
} }
if conn != nil {
conn.Close()
}
// IRC
osc.LogInfo(fmt.Sprintf("Checking %s IRC(6697)\n", hiddenService))
conn, err = utils.GetNetworkConnection(hiddenService, 6697, osc.TorProxyAddress, osc.Timeout)
if err != nil {
osc.LogInfo("Failed to connect to service on port 6697\n")
} else {
osc.LogInfo("Detected possible IRC (secure) instance\n")
// TODO: Actual Analysis
report.IRCDetected = true
}
if conn != nil {
conn.Close()
}
} }

View File

@ -1,29 +1,26 @@
package protocol package protocol
import ( import (
"fmt"
"github.com/s-rah/onionscan/config" "github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report" "github.com/s-rah/onionscan/report"
"github.com/s-rah/onionscan/utils" "h12.me/socks"
"log"
) )
type MongoDBProtocolScanner struct { type MongoDBProtocolScanner struct {
} }
func (rps *MongoDBProtocolScanner) ScanProtocol(hiddenService string, osc *config.OnionscanConfig, report *report.OnionScanReport) { func (rps *MongoDBProtocolScanner) ScanProtocol(hiddenService string, onionscanConfig *config.OnionscanConfig, report *report.OnionScanReport) {
// MongoDB // MongoDB
osc.LogInfo(fmt.Sprintf("Checking %s MongoDB(27017)\n", hiddenService)) log.Printf("Checking %s MongoDB(27017)\n", hiddenService)
conn, err := utils.GetNetworkConnection(hiddenService, 27017, osc.TorProxyAddress, osc.Timeout) _, err := socks.DialSocksProxy(socks.SOCKS5, onionscanConfig.TorProxyAddress)("", hiddenService+":27017")
if err != nil { if err != nil {
osc.LogInfo("Failed to connect to service on port 27017\n") log.Printf("Failed to connect to service on port 27017\n")
report.MongoDBDetected = false report.MongoDBDetected = false
} else { } else {
osc.LogInfo("Detected possible MongoDB instance\n") log.Printf("Detected possible MongoDB instance\n")
// TODO: Actual Analysis // TODO: Actual Analysis
report.MongoDBDetected = true report.MongoDBDetected = true
} }
if conn != nil {
conn.Close()
}
} }

View File

@ -1,28 +1,26 @@
package protocol package protocol
import ( import (
"fmt"
"github.com/s-rah/onionscan/config" "github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report" "github.com/s-rah/onionscan/report"
"github.com/s-rah/onionscan/utils" "h12.me/socks"
"log"
) )
type RicochetProtocolScanner struct { type RicochetProtocolScanner struct {
} }
func (rps *RicochetProtocolScanner) ScanProtocol(hiddenService string, osc *config.OnionscanConfig, report *report.OnionScanReport) { func (rps *RicochetProtocolScanner) ScanProtocol(hiddenService string, onionscanConfig *config.OnionscanConfig, report *report.OnionScanReport) {
// Ricochet // Ricochet
osc.LogInfo(fmt.Sprintf("Checking %s ricochet(9878)\n", hiddenService)) log.Printf("Checking %s ricochet(9878)\n", hiddenService)
conn, err := utils.GetNetworkConnection(hiddenService, 9878, osc.TorProxyAddress, osc.Timeout) _, err := socks.DialSocksProxy(socks.SOCKS5, onionscanConfig.TorProxyAddress)("", hiddenService+":9878")
if err != nil { if err != nil {
osc.LogInfo("Failed to connect to service on port 9878\n") log.Printf("Failed to connect to service on port 9878\n")
report.RicochetDetected = false report.RicochetDetected = false
} else { } else {
osc.LogInfo("Detected possible ricochet instance\n") log.Printf("Detected possible ricochet instance\n")
// TODO: Actual Analysis // TODO: Actual Analysis
report.RicochetDetected = true report.RicochetDetected = true
} }
if conn != nil {
conn.Close()
}
} }

View File

@ -1,38 +1,25 @@
package protocol package protocol
import ( import (
"bufio"
"crypto/sha1"
"encoding/hex"
"fmt"
"github.com/s-rah/onionscan/config" "github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report" "github.com/s-rah/onionscan/report"
"github.com/s-rah/onionscan/utils" "h12.me/socks"
"log"
) )
type SMTPProtocolScanner struct { type SMTPProtocolScanner struct {
} }
func (sps *SMTPProtocolScanner) ScanProtocol(hiddenService string, osc *config.OnionscanConfig, report *report.OnionScanReport) { func (sps *SMTPProtocolScanner) ScanProtocol(hiddenService string, onionscanConfig *config.OnionscanConfig, report *report.OnionScanReport) {
// SMTP // SMTP
osc.LogInfo(fmt.Sprintf("Checking %s SMTP(25)\n", hiddenService)) log.Printf("Checking %s SMTP(25)\n", hiddenService)
conn, err := utils.GetNetworkConnection(hiddenService, 25, osc.TorProxyAddress, osc.Timeout) _, err := socks.DialSocksProxy(socks.SOCKS5, onionscanConfig.TorProxyAddress)("", hiddenService+":25")
if err != nil { if err != nil {
osc.LogInfo("Failed to connect to service on port 25\n") log.Printf("Failed to connect to service on port 25\n")
report.SMTPDetected = false report.SMTPDetected = false
} else { } else {
// TODO SMTP Checking // TODO SMTP Checking
report.SMTPDetected = true report.SMTPDetected = true
reader := bufio.NewReader(conn)
banner, err := reader.ReadString('\n')
if err == nil {
report.SMTPBanner = banner
hash := sha1.Sum([]byte(banner))
report.SMTPFingerprint = hex.EncodeToString(hash[:])
osc.LogInfo(fmt.Sprintf("Found SMTP Banner: %s (%s)", banner, report.SMTPFingerprint))
}
}
if conn != nil {
conn.Close()
} }
} }

View File

@ -1,30 +1,27 @@
package protocol package protocol
import ( import (
"bufio"
"crypto/md5" "crypto/md5"
"errors" "errors"
"fmt" "fmt"
"github.com/s-rah/onionscan/config" "github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report" "github.com/s-rah/onionscan/report"
"github.com/s-rah/onionscan/utils"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"h12.me/socks"
"log"
"net" "net"
) )
type SSHProtocolScanner struct { type SSHProtocolScanner struct {
} }
func (sps *SSHProtocolScanner) ScanProtocol(hiddenService string, osc *config.OnionscanConfig, report *report.OnionScanReport) { func (sps *SSHProtocolScanner) ScanProtocol(hiddenService string, onionscanConfig *config.OnionscanConfig, report *report.OnionScanReport) {
// SSH // SSH
osc.LogInfo(fmt.Sprintf("Checking %s ssh(22)\n", hiddenService)) log.Printf("Checking %s ssh(22)\n", hiddenService)
conn, err := utils.GetNetworkConnection(hiddenService, 22, osc.TorProxyAddress, osc.Timeout) conn, err := socks.DialSocksProxy(socks.SOCKS5, onionscanConfig.TorProxyAddress)("", hiddenService+":22")
if err != nil { if err != nil {
osc.LogInfo("Failed to connect to service on port 22\n") log.Printf("Failed to connect to service on port 22\n")
report.SSHDetected = false report.SSHDetected = false
if conn != nil {
conn.Close()
}
} else { } else {
// TODO SSH Checking // TODO SSH Checking
report.SSHDetected = true report.SSHDetected = true
@ -44,26 +41,13 @@ func (sps *SSHProtocolScanner) ScanProtocol(hiddenService string, osc *config.On
} }
} }
report.SSHKey = fingerprint report.SSHKey = fingerprint
osc.LogInfo(fmt.Sprintf("Found SSH Key %s\n", fingerprint)) log.Printf("Found SSH Key %s\n", fingerprint)
// We don't want to continue // We don't want to continue
return errors.New("error") return errors.New("error")
}, },
} }
ssh.NewClientConn(conn, hiddenService+":22", config) ssh.NewClientConn(conn, hiddenService+":22", config)
if conn != nil {
conn.Close()
}
conn, err = utils.GetNetworkConnection(hiddenService, 22, osc.TorProxyAddress, osc.Timeout)
if err == nil {
reader := bufio.NewReader(conn)
banner, err := reader.ReadString('\n')
if err == nil {
report.SSHBanner = banner
osc.LogInfo(fmt.Sprintf("Found SSH Banner: %s (%s)", banner))
}
}
if conn != nil {
conn.Close()
}
} }
} }

View File

@ -1,37 +0,0 @@
package protocol
import (
"crypto/tls"
"fmt"
"github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report"
"github.com/s-rah/onionscan/utils"
)
type TLSProtocolScanner struct {
}
func (sps *TLSProtocolScanner) ScanProtocol(hiddenService string, osc *config.OnionscanConfig, report *report.OnionScanReport) {
osc.LogInfo(fmt.Sprintf("Checking %s TLS(443)\n", hiddenService))
conn, err := utils.GetNetworkConnection(hiddenService, 443, osc.TorProxyAddress, osc.Timeout)
if err != nil {
osc.LogInfo("Failed to connect to service on port 443\n")
report.TLSDetected = false
} else {
osc.LogInfo("Found TLS Endpoint\n")
report.TLSDetected = true
config := &tls.Config{
InsecureSkipVerify: true,
}
tlsConn := tls.Client(conn, config)
tlsConn.Write([]byte("GET / HTTP/1.1\r\n\r\n"))
for _, certificate := range tlsConn.ConnectionState().PeerCertificates {
osc.LogInfo(fmt.Sprintf("Found Certificate %v \n", certificate))
report.Certificates = append(report.Certificates, *certificate)
}
tlsConn.Close()
}
if conn != nil {
conn.Close()
}
}

View File

@ -1,28 +0,0 @@
package protocol
import (
"fmt"
"github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report"
"github.com/s-rah/onionscan/utils"
)
type VNCProtocolScanner struct {
}
func (vncps *VNCProtocolScanner) ScanProtocol(hiddenService string, osc *config.OnionscanConfig, report *report.OnionScanReport) {
// MongoDB
osc.LogInfo(fmt.Sprintf("Checking %s VNC(5900)\n", hiddenService))
conn, err := utils.GetNetworkConnection(hiddenService, 5900, osc.TorProxyAddress, osc.Timeout)
if err != nil {
osc.LogInfo("Failed to connect to service on port 5900\n")
report.VNCDetected = false
} else {
osc.LogInfo("Detected possible VNC instance\n")
// TODO: Actual Analysis
report.VNCDetected = true
}
if conn != nil {
conn.Close()
}
}

View File

@ -1,41 +0,0 @@
package protocol
import (
"fmt"
"github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report"
"github.com/s-rah/onionscan/utils"
)
type XMPPProtocolScanner struct {
}
func (rps *XMPPProtocolScanner) ScanProtocol(hiddenService string, osc *config.OnionscanConfig, report *report.OnionScanReport) {
// XMPP
osc.LogInfo(fmt.Sprintf("Checking %s XMPP(5222)\n", hiddenService))
conn, err := utils.GetNetworkConnection(hiddenService, 5222, osc.TorProxyAddress, osc.Timeout)
if err != nil {
osc.LogInfo("Failed to connect to service on port 5222\n")
report.XMPPDetected = false
} else {
osc.LogInfo("Detected possible XMPP instance\n")
// TODO: Actual Analysis
report.XMPPDetected = true
}
if conn != nil {
conn.Close()
}
// XMPP
osc.LogInfo(fmt.Sprintf("Checking %s XMPP(5223)\n", hiddenService))
conn, err = utils.GetNetworkConnection(hiddenService, 5223, osc.TorProxyAddress, osc.Timeout)
if err != nil {
osc.LogInfo("Failed to connect to service on port 5223\n")
} else {
osc.LogInfo("Detected possible XMPP (secure) instance\n")
// TODO: Actual Analysis
report.XMPPDetected = true
}
if conn != nil {
conn.Close()
}
}

View File

@ -1,11 +1,9 @@
package report package report
import ( import (
"crypto/x509"
"encoding/json" "encoding/json"
"github.com/s-rah/onionscan/utils" "github.com/s-rah/onionscan/utils"
"io/ioutil" "io/ioutil"
"time"
) )
type ExifTag struct { type ExifTag struct {
@ -19,69 +17,42 @@ type ExifImage struct {
} }
type PGPKey struct { type PGPKey struct {
ArmoredKey string `json:"armoredKey"` ArmoredKey string `json:"armoredKey"`
Identity string `json:"identity"` Identity string `json:"identity"`
FingerPrint string `json:"fingerprint"` FingerPrint string `json:"fingerprint"`
} }
type OnionScanReport struct { type OnionScanReport struct {
HiddenService string `json:"hiddenService"` WebDetected bool `json:"webDetected"`
DateScanned time.Time `json:"dateScanned"` SSHDetected bool `json:"sshDetected"`
RicochetDetected bool `json:"ricochetDetected"`
IRCDetected bool `json:"ircDetected"`
FTPDetected bool `json:"ftpDetected"`
SMTPDetected bool `json:"smtpDetected"`
// Summary BitcoinDetected bool `json:"bitcoinDetected"`
WebDetected bool `json:"webDetected"` MongoDBDetected bool `json:"mongodbDetected"`
TLSDetected bool `json:"tlsDetected"`
SSHDetected bool `json:"sshDetected"`
RicochetDetected bool `json:"ricochetDetected"`
IRCDetected bool `json:"ircDetected"`
FTPDetected bool `json:"ftpDetected"`
SMTPDetected bool `json:"smtpDetected"`
BitcoinDetected bool `json:"bitcoinDetected"`
MongoDBDetected bool `json:"mongodbDetected"`
VNCDetected bool `json:"vncDetected"`
XMPPDetected bool `json:"xmppDetected"`
SkynetDetected bool `json:"skynetDetected"`
PrivateKeyDetected bool `json:"privateKeyDetected"`
// Web Specific HiddenService string `json:"hiddenService"`
ServerPoweredBy string `json:"serverPoweredBy"` ServerPoweredBy string `json:"serverPoweredBy"`
ServerVersion string `json:"serverVersion"` ServerVersion string `json:"serverVersion"`
FoundApacheModStatus bool `json:"foundApacheModStatus"` FoundApacheModStatus bool `json:"foundApacheModStatus"`
RelatedOnionServices []string `json:"relatedOnionServices"` RelatedOnionServices []string `json:"relatedOnionServices"`
RelatedClearnetDomains []string `json:"relatedOnionDomains"` RelatedClearnetDomains []string `json:"relatedOnionDomains"`
LinkedSites []string `json:"linkedSites"` LinkedSites []string `json:"linkedSites"`
InternalPages []string `json:"internalPages"` InternalPages []string `json:"InternalPages"`
IP []string `json:"ipAddresses"` IP []string `json:"ipAddresses"`
OpenDirectories []string `json:"openDirectories"` OpenDirectories []string `json:"openDirectories"`
ExifImages []ExifImage `json:"exifImages"` ExifImages []ExifImage `json:"exifImages"`
InterestingFiles []string `json:"interestingFiles"` InterestingFiles []string `json:"interestingFiles"`
PageReferencedDirectories []string `json:"pageReferencedDirectories"` PageReferencedDirectories []string `json:"pageReferencedDirectories"`
PGPKeys []PGPKey `json:"pgpKeys"` PGPKeys []PGPKey `json:"pgpKeys"`
Hashes []string `json:"hashes"`
Snapshot string `json:"snapshot"`
PageTitle string `json:"pageTitle"`
ResponseHeaders map[string]string `json:"responseHeaders"`
// TLS Hashes []string `json:"hashes"`
Certificates []x509.Certificate `json:"certificates"` SSHKey string `json:"sshKey"`
Snapshot string `json:"snapshot"`
//Bitcoin PageTitle string `json:"pageTitle"`
BitcoinAddresses []string `json:"bitcoinAddresses"` ResponseHeaders map[string]string `json:"responseHeaders"`
// SSH
SSHKey string `json:"sshKey"`
SSHBanner string `json:"sshBanner"`
// FTP
FTPFingerprint string `json:"ftpFingerprint"`
FTPBanner string `json:"ftpBanner"`
// SMTP
SMTPFingerprint string `json:"smtpFingerprint"`
SMTPBanner string `json:"smtpBanner"`
NextAction string `json:"lastAction"`
TimedOut bool
} }
func LoadReportFromFile(filename string) (OnionScanReport, error) { func LoadReportFromFile(filename string) (OnionScanReport, error) {
@ -95,11 +66,7 @@ func LoadReportFromFile(filename string) (OnionScanReport, error) {
} }
func NewOnionScanReport(hiddenService string) *OnionScanReport { func NewOnionScanReport(hiddenService string) *OnionScanReport {
report := new(OnionScanReport) return &OnionScanReport{HiddenService: hiddenService, ResponseHeaders: make(map[string]string)}
report.HiddenService = hiddenService
report.ResponseHeaders = make(map[string]string)
report.DateScanned = time.Now()
return report
} }
func (osr *OnionScanReport) AddOpenDirectory(dir string) { func (osr *OnionScanReport) AddOpenDirectory(dir string) {

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"time"
) )
func GenerateJsonReport(reportFile string, report *OnionScanReport) { func GenerateJsonReport(reportFile string, report *OnionScanReport) {
@ -16,11 +15,9 @@ func GenerateJsonReport(reportFile string, report *OnionScanReport) {
if len(reportFile) > 0 { if len(reportFile) > 0 {
f, err := os.Create(reportFile) f, err := os.Create(reportFile)
if err != nil {
for err != nil { log.Fatalf("Cannot create report file: %s", err)
log.Printf("Cannot create report file: %s...trying again in 5 seconds...", err) panic(err)
time.Sleep(time.Second * 5)
f, err = os.Create(reportFile)
} }
defer f.Close() defer f.Close()
@ -176,11 +173,9 @@ func GenerateSimpleReport(reportFile string, report *OnionScanReport) {
if len(reportFile) > 0 { if len(reportFile) > 0 {
f, err := os.Create(reportFile) f, err := os.Create(reportFile)
if err != nil {
for err != nil { log.Fatalf("Cannot create report file: %s", err)
log.Printf("Cannot create report file: %s...trying again in 5 seconds...", err) panic(err)
time.Sleep(time.Second * 5)
f, err = os.Create(reportFile)
} }
defer f.Close() defer f.Close()

View File

@ -1,59 +1,58 @@
package scans package scans
import ( import (
"fmt"
"github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report" "github.com/s-rah/onionscan/report"
"github.com/s-rah/onionscan/utils" "github.com/s-rah/onionscan/utils"
"log"
"regexp" "regexp"
"strings" "strings"
) )
func ApacheModStatus(scan Scanner, page string, status int, contents string, report *report.OnionScanReport, osc *config.OnionscanConfig) { func ApacheModStatus(scan Scanner, page string, status int, contents string, report *report.OnionScanReport) {
if status == 200 { if status == 200 {
r := regexp.MustCompile(`Server Version: (.*)</dt>`) r := regexp.MustCompile(`Server Version: (.*)</dt>`)
serverVersion := r.FindStringSubmatch(string(contents)) serverVersion := r.FindStringSubmatch(string(contents))
// Check if this looks like a mod_status page. Sometimes sites simply load their index. // Check if this looks like a mod_status page. Sometimes sites simply load their index.
if len(serverVersion) > 1 { if len(serverVersion) > 1 {
osc.LogInfo("Detected Apache mod_status Exposed...\033[091mAlert!\033[0m\n") log.Printf("Detected Apache mod_status Exposed...\033[091mAlert!\033[0m\n")
report.FoundApacheModStatus = true report.FoundApacheModStatus = true
osc.LogInfo(fmt.Sprintf("\t Using mod_status Server Version: %s\n", serverVersion[1])) log.Printf("\t Using mod_status Server Version: %s\n", serverVersion[1])
report.ServerVersion = serverVersion[1] report.ServerVersion = serverVersion[1]
// Check for co-hosted onion services. // Check for co-hosted onion services.
osc.LogInfo("Scanning for Co-Hosted Onions\n") log.Printf("Scanning for Co-Hosted Onions\n")
r = regexp.MustCompile(`[a-z0-9]+.onion(:[0-9]{0-5})?`) r = regexp.MustCompile(`[a-z0-9]+.onion(:[0-9]{0-5})?`)
foundServices := r.FindAllString(string(contents), -1) foundServices := r.FindAllString(string(contents), -1)
utils.RemoveDuplicates(&foundServices) utils.RemoveDuplicates(&foundServices)
for _, onion := range foundServices { for _, onion := range foundServices {
if onion != report.HiddenService { if onion != report.HiddenService {
osc.LogInfo(fmt.Sprintf("\t \033[091mAlert!\033[0m Found Co-Hosted Onions: %s\n", onion)) log.Printf("\t \033[091mAlert!\033[0m Found Co-Hosted Onions: %s\n", onion)
report.AddRelatedOnionService(onion) report.AddRelatedOnionService(onion)
} }
} }
// Check for co-hosted onion services. // Check for co-hosted onion services.
osc.LogInfo("Scanning for Co-Hosted Clearnet Domains\n") log.Printf("Scanning for Co-Hosted Clearnet Domains\n")
r = regexp.MustCompile(`>(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})`) r = regexp.MustCompile(`>(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})`)
foundServices = r.FindAllString(string(contents), -1) foundServices = r.FindAllString(string(contents), -1)
utils.RemoveDuplicates(&foundServices) utils.RemoveDuplicates(&foundServices)
for _, domain := range foundServices { for _, domain := range foundServices {
if strings.Contains(domain, ".onion") == false { if strings.Contains(domain, ".onion") == false {
osc.LogInfo(fmt.Sprintf("\t \033[091mAlert!\033[0m Found Co-Hosted Service: %s\n", domain[1:])) log.Printf("\t \033[091mAlert!\033[0m Found Co-Hosted Service: %s\n", domain[1:])
report.AddRelatedClearnetDomain(domain[4:]) report.AddRelatedClearnetDomain(domain[4:])
} }
} }
// Check for IP Addresses // Check for IP Addresses
osc.LogInfo("Scanning for IP Addresses (clearweb clients, and servers)\n") log.Printf("Scanning for IP Addresses (clearweb clients, and servers)\n")
r = regexp.MustCompile(`(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`) r = regexp.MustCompile(`(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`)
foundIPs := r.FindAllString(string(contents), -1) foundIPs := r.FindAllString(string(contents), -1)
utils.RemoveDuplicates(&foundIPs) utils.RemoveDuplicates(&foundIPs)
for _, ip := range foundIPs { for _, ip := range foundIPs {
if ip != "127.0.0.1" { if ip != "127.0.0.1" {
osc.LogInfo(fmt.Sprintf("\t \033[091mAlert!\033[0m Found IP Address : %s\n", ip)) log.Printf("\t \033[091mAlert!\033[0m Found IP Address : %s\n", ip)
report.AddIPAddress(ip) report.AddIPAddress(ip)
} }
} }
@ -61,7 +60,7 @@ func ApacheModStatus(scan Scanner, page string, status int, contents string, rep
} }
} }
if !report.FoundApacheModStatus { if !report.FoundApacheModStatus {
osc.LogInfo("\tApache mod_status Not Exposed...\033[92mGood!\033[0m\n") log.Printf("\tApache mod_status Not Exposed...\033[92mGood!\033[0m\n")
report.FoundApacheModStatus = false report.FoundApacheModStatus = false
} }
} }

View File

@ -1,20 +0,0 @@
package scans
import (
"github.com/s-rah/onionscan/report"
"log"
"regexp"
)
type BitcoinContentScan struct {
}
func (cs *BitcoinContentScan) ScanContent(content string, report *report.OnionScanReport) {
log.Printf("Scanning for Bitcoin Address\n")
bitcoinAddressRegexp := regexp.MustCompile("[1|3][A-Za-z0-9]{25,34}")
foundBitcoinAddress := bitcoinAddressRegexp.FindAllString(content, -1)
for _, ba := range foundBitcoinAddress {
log.Printf("Found Bitcoin Address: %s", ba)
report.BitcoinAddresses = append(report.BitcoinAddresses, ba)
}
}

View File

@ -1,36 +1,35 @@
package scans package scans
import ( import (
"fmt"
"github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report" "github.com/s-rah/onionscan/report"
"log"
"regexp" "regexp"
"strings" "strings"
) )
func CheckDirectoryListing(depth int) func(Scanner, string, int, string, *report.OnionScanReport, *config.OnionscanConfig) { func CheckDirectoryListing(depth int) func(Scanner, string, int, string, *report.OnionScanReport) {
return func(scan Scanner, dir string, status int, contents string, report *report.OnionScanReport, osc *config.OnionscanConfig) { return func(scan Scanner, dir string, status int, contents string, report *report.OnionScanReport) {
CheckDirectoryListingDepth(scan, dir, status, depth, contents, report, osc) CheckDirectoryListingDepth(scan, dir, status, depth, contents, report)
} }
} }
func CheckDirectoryListingDepth(scan Scanner, dir string, status int, depth int, contents string, report *report.OnionScanReport, osc *config.OnionscanConfig) { func CheckDirectoryListingDepth(scan Scanner, dir string, status int, depth int, contents string, report *report.OnionScanReport) {
if status == 200 && strings.Contains(string(contents), "Index of "+dir) { if status == 200 && strings.Contains(string(contents), "Index of "+dir) {
osc.LogInfo(fmt.Sprintf("Detected Open Directory %s...\033[091mAlert!\033[0m\n", dir)) log.Printf("Detected Open Directory %s...\033[091mAlert!\033[0m\n", dir)
report.AddOpenDirectory(dir) report.AddOpenDirectory(dir)
r := regexp.MustCompile(`href="((.*?\.jpg)|(.*?\.png)|(.*?\.jpeg)|(.*?\.gif))"`) r := regexp.MustCompile(`href="((.*?\.jpg)|(.*?\.png)|(.*?\.jpeg)|(.*?\.gif))"`)
foundImages := r.FindAllStringSubmatch(string(contents), -1) foundImages := r.FindAllStringSubmatch(string(contents), -1)
for _, image := range foundImages { for _, image := range foundImages {
osc.LogInfo(fmt.Sprintf("\t Found image %s/%s\n", dir, image[1])) log.Printf("\t Found image %s/%s\n", dir, image[1])
scan.ScanPage(report.HiddenService, dir+"/"+image[1], report, osc, CheckExif) scan.ScanPage(report.HiddenService, dir+"/"+image[1], report, CheckExif)
} }
r = regexp.MustCompile(`href="((.*\.zip)|(.*\.tar)|(.*\.gz)|(.*\.pst)|(.*\.txt))"`) r = regexp.MustCompile(`href="((.*\.zip)|(.*\.tar)|(.*\.gz)|(.*\.pst)|(.*\.txt))"`)
interestingFiles := r.FindAllStringSubmatch(string(contents), -1) interestingFiles := r.FindAllStringSubmatch(string(contents), -1)
for _, file := range interestingFiles { for _, file := range interestingFiles {
osc.LogInfo(fmt.Sprintf("\t Found interesting file %s/%s\n", dir, file[1])) log.Printf("\t Found interesting file %s/%s\n", dir, file[1])
//TODO: We can do further analysis here, for now, just report them. //TODO: We can do further analysis here, for now, just report them.
report.AddInterestingFile(dir + "/" + file[1]) report.AddInterestingFile(dir + "/" + file[1])
} }
@ -38,14 +37,14 @@ func CheckDirectoryListingDepth(scan Scanner, dir string, status int, depth int,
r = regexp.MustCompile(`href="([^/](.*?))/"`) r = regexp.MustCompile(`href="([^/](.*?))/"`)
subDir := r.FindAllStringSubmatch(string(contents), -1) subDir := r.FindAllStringSubmatch(string(contents), -1)
for _, file := range subDir { for _, file := range subDir {
osc.LogInfo(fmt.Sprintf("\t Found subdir %s/%s\n", dir, file[1])) log.Printf("\t Found subdir %s/%s\n", dir, file[1])
//TODO: We can do further analysis here, for now, just report them. //TODO: We can do further analysis here, for now, just report them.
if depth > 0 { if depth > 0 {
scan.ScanPage(report.HiddenService, dir+"/"+file[1], report, osc, CheckDirectoryListing(depth-1)) scan.ScanPage(report.HiddenService, dir+"/"+file[1], report, CheckDirectoryListing(depth-1))
} }
} }
} else { } else {
osc.LogInfo(fmt.Sprintf("Directory %s either doesn't exist or is not readable\n", dir)) log.Printf("Directory %s either doesn't exist or is not readable\n", dir)
} }
} }

View File

@ -1,15 +1,14 @@
package scans package scans
import ( import (
"fmt"
"github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report" "github.com/s-rah/onionscan/report"
"github.com/xiam/exif" "github.com/xiam/exif"
"io" "io"
"log"
"strings" "strings"
) )
func CheckExif(scan Scanner, page string, status int, contents string, report *report.OnionScanReport, osc *config.OnionscanConfig) { func CheckExif(scan Scanner, page string, status int, contents string, report *report.OnionScanReport) {
if status == 200 { if status == 200 {
reader := exif.New() reader := exif.New()
_, err := io.Copy(reader, strings.NewReader(contents)) _, err := io.Copy(reader, strings.NewReader(contents))
@ -32,7 +31,7 @@ func CheckExif(scan Scanner, page string, status int, contents string, report *r
report.AddExifImage(page) report.AddExifImage(page)
for name, val := range reader.Tags { for name, val := range reader.Tags {
osc.LogInfo(fmt.Sprintf("\t \033[091mAlert!\033[0m Found Exif Tag%s: %s\n", name, val)) log.Printf("\t \033[091mAlert!\033[0m Found Exif Tag%s: %s\n", name, val)
report.AddExifTag(name, val) report.AddExifTag(name, val)
} }
} }

View File

@ -3,34 +3,38 @@ package scans
import ( import (
"github.com/s-rah/onionscan/report" "github.com/s-rah/onionscan/report"
"golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp"
"log"
"regexp" "regexp"
"strings" "strings"
"fmt"
) )
type PGPContentScan struct { type PGPContentScan struct {
} }
func (cs *PGPContentScan) ScanContent(content string, report *report.OnionScanReport) { func (cs *PGPContentScan) ScanContent(content string, report *report.OnionScanReport) {
//log.Printf("Scanning for PGP Key\n") log.Printf("Scanning for PGP Key\n")
pgpRegexp := regexp.MustCompile("-----BEGIN PGP PUBLIC KEY BLOCK-----((?s).*)-----END PGP PUBLIC KEY BLOCK-----") pgpRegexp := regexp.MustCompile("-----BEGIN PGP PUBLIC KEY BLOCK-----((?s).*)-----END PGP PUBLIC KEY BLOCK-----")
foundPGP := pgpRegexp.FindAllString(content, -1) foundPGP := pgpRegexp.FindAllString(content, -1)
for _, keyString := range foundPGP { for _, keyString := range foundPGP {
keys, err := openpgp.ReadArmoredKeyRing(strings.NewReader(keyString)) keys, err := openpgp.ReadArmoredKeyRing(strings.NewReader(keyString));
if err != nil { if err != nil {
// log.Printf("ERROR: %s\n", err) log.Printf("ERROR: %s\n", err)
continue continue
} }
if len(keys) < 1 || len(keys[0].Subkeys) < 1 || len(keys[0].Identities) < 1 { if len(keys) < 1 || len(keys[0].Subkeys) < 1 || len(keys[0].Identities) < 1{
// log.Printf("ERROR: failed to accept key\n") log.Printf("ERROR: failed to accept key\n")
continue continue
} }
var identity string var identity string
for identity = range keys[0].Identities { for identity, _ = range keys[0].Identities {
break break
} }
// log.Printf("\tFound PGP Key fingerprint: %s belonging to %s", keys[0].Subkeys[0].PublicKey.KeyIdShortString(), identity) var fingerprint string
fingerprint = fmt.Sprintf("%X", keys[0].Subkeys[0].PublicKey.Fingerprint)
log.Printf("\tFound PGP Key fingerprint: %s belonging to %s", fingerprint, identity)
report.AddPGPKey(keyString, identity, keys[0].Subkeys[0].PublicKey.KeyIdShortString()) report.AddPGPKey(keyString, identity, fingerprint)
} }
} }

View File

@ -1,15 +0,0 @@
package scans
import (
"fmt"
"github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report"
)
func PrivateKeyScan(scan Scanner, page string, status int, contents string, report *report.OnionScanReport, osc *config.OnionscanConfig) {
osc.LogInfo(fmt.Sprintf("Scanning %s\n", page))
if status == 200 {
osc.LogInfo(fmt.Sprintf("\tPrivate Key %s is Accessible!!\n", page))
report.PrivateKeyDetected = true
}
}

View File

@ -1,11 +1,9 @@
package scans package scans
import ( import (
"github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report" "github.com/s-rah/onionscan/report"
) )
type Scanner interface { type Scanner interface {
ScanPage(string, string, *report.OnionScanReport, *config.OnionscanConfig, func(Scanner, string, int, string, *report.OnionScanReport, *config.OnionscanConfig)) ScanPage(string, string, *report.OnionScanReport, func(Scanner, string, int, string, *report.OnionScanReport))
ScrapePage(string, string) (error, []byte, int)
} }

View File

@ -3,20 +3,19 @@ package scans
import ( import (
"crypto/sha1" "crypto/sha1"
"encoding/hex" "encoding/hex"
"fmt"
"github.com/s-rah/onionscan/config"
"github.com/s-rah/onionscan/report" "github.com/s-rah/onionscan/report"
"github.com/s-rah/onionscan/utils" "github.com/s-rah/onionscan/utils"
"golang.org/x/net/html" "golang.org/x/net/html"
"log"
"net/url" "net/url"
"regexp" "regexp"
"strings" "strings"
) )
func StandardPageScan(scan Scanner, page string, status int, contents string, report *report.OnionScanReport, osc *config.OnionscanConfig) { func StandardPageScan(scan Scanner, page string, status int, contents string, report *report.OnionScanReport) {
osc.LogInfo(fmt.Sprintf("Scanning %s\n", page)) log.Printf("Scanning %s\n", page)
if status == 200 { if status == 200 {
osc.LogInfo(fmt.Sprintf("\tPage %s is Accessible\n", page)) log.Printf("\tPage %s is Accessible\n", page)
hash := sha1.Sum([]byte(contents)) hash := sha1.Sum([]byte(contents))
report.Hashes = append(report.Hashes, hex.EncodeToString(hash[:])) report.Hashes = append(report.Hashes, hex.EncodeToString(hash[:]))
@ -28,16 +27,14 @@ func StandardPageScan(scan Scanner, page string, status int, contents string, re
var startIndex = strings.Index(contents, "<title>") var startIndex = strings.Index(contents, "<title>")
var endIndex = strings.Index(contents, "</title>") var endIndex = strings.Index(contents, "</title>")
var pageTitle = contents[startIndex+len("<title>") : endIndex] var pageTitle = contents[startIndex+len("<title>") : endIndex]
osc.LogInfo(fmt.Sprintf("\tPage Title: %s\n", pageTitle)) log.Printf("\tPage Title: %s\n", pageTitle)
report.PageTitle = pageTitle report.PageTitle = pageTitle
} }
new(PGPContentScan).ScanContent(contents, report) new(PGPContentScan).ScanContent(contents, report)
//new(BitcoinContentScan).ScanContent(contents, report)
osc.LogInfo("\tScanning for Images\n") log.Printf("\tScanning for Images\n")
var domains []string domains := utils.ExtractDomains(contents)
var cssLinks []string
// parser based on http://schier.co/blog/2015/04/26/a-simple-web-scraper-in-go.html // parser based on http://schier.co/blog/2015/04/26/a-simple-web-scraper-in-go.html
z := html.NewTokenizer(strings.NewReader(contents)) z := html.NewTokenizer(strings.NewReader(contents))
@ -52,8 +49,8 @@ func StandardPageScan(scan Scanner, page string, status int, contents string, re
// TODO: don't crawl links with nofollow // TODO: don't crawl links with nofollow
if tt == html.StartTagToken { if tt == html.StartTagToken {
// links isLink := t.Data == "a"
if t.Data == "a" { if isLink {
linkUrl := utils.GetAttribute(t, "href") linkUrl := utils.GetAttribute(t, "href")
if len(linkUrl) > 1 { if len(linkUrl) > 1 {
domains = append(domains, linkUrl) domains = append(domains, linkUrl)
@ -61,74 +58,56 @@ func StandardPageScan(scan Scanner, page string, status int, contents string, re
} }
} }
// css <link> isImage := t.Data == "img"
if t.Data == "link" && utils.GetAttribute(t, "rel") == "stylesheet" { if isImage {
cssLinks = append(cssLinks, utils.GetAttribute(t, "href"))
}
// images
if t.Data == "img" {
imageUrl := utils.GetAttribute(t, "src") imageUrl := utils.GetAttribute(t, "src")
baseUrl, err := url.Parse(imageUrl) baseUrl, _ := url.Parse(imageUrl)
if err == nil { if utils.WithoutSubdomains(baseUrl.Host) == utils.WithoutSubdomains(report.HiddenService) {
if utils.WithoutSubdomains(baseUrl.Host) == utils.WithoutSubdomains(report.HiddenService) { scan.ScanPage(report.HiddenService, utils.WithoutProtocol(imageUrl), report, CheckExif)
scan.ScanPage(report.HiddenService, utils.WithoutProtocol(imageUrl), report, osc, CheckExif) log.Printf("\t Found internal image %s\n", imageUrl)
osc.LogInfo(fmt.Sprintf("\t Found internal image %s\n", imageUrl))
} else {
osc.LogInfo(fmt.Sprintf("\t Not scanning remote image %s\n", imageUrl))
}
}
}
}
osc.LogInfo("\tScanning for CSS Fonts and Background Images\n")
utils.RemoveDuplicates(&cssLinks)
for _, cssUrl := range cssLinks {
osc.LogInfo(fmt.Sprintf("\tScanning CSS file: %s\n", cssUrl))
_, cssContents, _ := scan.ScrapePage(report.HiddenService, utils.WithoutProtocol(cssUrl))
domains = append(domains, utils.ExtractDomains(string(cssContents))[:]...)
}
osc.LogInfo("\tScanning for Links\n")
domains = append(domains, utils.ExtractDomains(contents)...)
utils.RemoveDuplicates(&domains)
for _, domain := range domains {
baseUrl, err := url.Parse(domain)
if err == nil {
if baseUrl.Host != "" && utils.WithoutSubdomains(baseUrl.Host) != utils.WithoutSubdomains(report.HiddenService) {
osc.LogInfo(fmt.Sprintf("Found Related URL %s\n", domain))
// TODO: Lots of information here which needs to be processed.
// * Links to standard sites - google / bitpay etc.
// * Links to other onion sites
// * Links to obscure clearnet sites.
report.AddLinkedSite(baseUrl.Host)
} else { } else {
// * Process FQDN internal links log.Printf("\t Not scanning remote image %s\n", imageUrl)
osc.LogInfo(fmt.Sprintf("Found Internal URL %s\n", domain))
report.AddInternalPage(baseUrl.Host)
} }
} }
} }
osc.LogInfo("\tScanning for Referenced Directories\n") log.Printf("\tScanning for Links\n")
for _, domain := range domains {
baseUrl, _ := url.Parse(domain)
if baseUrl.Host != "" && utils.WithoutSubdomains(baseUrl.Host) != utils.WithoutSubdomains(report.HiddenService) {
log.Printf("Found Related URL %s\n", domain)
// TODO: Lots of information here which needs to be processed.
// * Links to standard sites - google / bitpay etc.
// * Links to other onion sites
// * Links to obscure clearnet sites.
report.AddLinkedSite(baseUrl.Host)
} else {
// * Process FQDN internal links
log.Printf("Found Internal URL %s\n", domain)
report.AddInternalPage(baseUrl.Host)
}
}
log.Printf("\tScanning for Referenced Directories\n")
r := regexp.MustCompile("(src|href)=\"([^\"]*)\"") r := regexp.MustCompile("(src|href)=\"([^\"]*)\"")
foundPaths := r.FindAllStringSubmatch(string(contents), -1) foundPaths := r.FindAllStringSubmatch(string(contents), -1)
for _, regexpResults := range foundPaths { for _, regexpResults := range foundPaths {
path := regexpResults[2] path := regexpResults[2]
if (strings.HasPrefix(path, "http") || strings.HasPrefix(path, "//")) && !strings.Contains(path, utils.WithoutSubdomains(report.HiddenService)) { if strings.HasPrefix(path, "http") && !strings.Contains(path, utils.WithoutSubdomains(report.HiddenService)) {
continue continue
} }
term := strings.LastIndex(path, "/") term := strings.LastIndex(path, "/")
if term > 0 { if term > 0 {
osc.LogInfo(fmt.Sprintf("\t Found Referenced Directory %s\n", path[:term])) log.Printf("\t Found Referenced Directory %s\n", path[:term])
report.AddPageReferencedDirectory(utils.WithoutProtocol(path[:term])) report.AddPageReferencedDirectory(utils.WithoutProtocol(path[:term]))
} }
} }
} else if status == 403 { } else if status == 403 {
osc.LogInfo(fmt.Sprintf("\tPage %s%s is Forbidden\n", report.HiddenService, page)) log.Printf("\tPage %s%s is Forbidden\n", report.HiddenService, page)
} else if status == 404 { } else if status == 404 {
osc.LogInfo(fmt.Sprintf("\tPage %s%s is Does Not Exist\n", report.HiddenService, page)) log.Printf("\tPage %s%s is Does Not Exist\n", report.HiddenService, page)
} }
} }

View File

@ -1,17 +0,0 @@
package utils
import (
"h12.me/socks"
"net"
"strconv"
"time"
)
func GetNetworkConnection(onionService string, port int, proxy string, timeout time.Duration) (net.Conn, error) {
portNumber := strconv.Itoa(port)
conn, err := socks.DialSocksProxy(socks.SOCKS5, proxy)("", onionService+":"+portNumber)
if err == nil {
conn.SetDeadline(time.Now().Add(timeout))
}
return conn, err
}

View File

@ -2,23 +2,11 @@ package utils
import ( import (
"github.com/mvdan/xurls" "github.com/mvdan/xurls"
"regexp"
"strings" "strings"
) )
func ExtractDomains(content string) []string { func ExtractDomains(content string) []string {
domains := xurls.Strict.FindAllString(content, -1) return xurls.Strict.FindAllString(content, -1)
cssurlregex := regexp.MustCompile(`(?i)url\((.*?)\)`)
cssDomains := cssurlregex.FindAllString(content, -1)
for _, cssDomain := range cssDomains {
if strings.HasPrefix(strings.ToLower(cssDomain), "url(") {
cssDomain = cssDomain[4 : len(cssDomain)-1]
}
if !strings.HasSuffix(cssDomain, ":before") && !strings.HasSuffix(cssDomain, ":after") {
domains = append(domains, cssDomain)
}
}
return domains
} }
func WithoutSubdomains(urlhost string) string { func WithoutSubdomains(urlhost string) string {
@ -37,8 +25,5 @@ func WithoutProtocol(url string) string {
if strings.HasPrefix(url, "https://") { if strings.HasPrefix(url, "https://") {
return url[8:] return url[8:]
} }
if strings.HasPrefix(url, "//") {
return url[2:]
}
return url return url
} }