Skip to content

Commit

Permalink
Allow ip addresses as input and prepare v0.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
thetillhoff committed Jul 16, 2023
1 parent 5ebd853 commit cb62463
Show file tree
Hide file tree
Showing 12 changed files with 90 additions and 23 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# CHANGELOG

## v0.2.0
- Status code 308 added to valid status codes (only 301 before)
- Now properly checks certificate validity at correct step (not when checking status codes)
- Allow IP addresses (IPv4 and IPv6) as input. If that's the case, dns checking and dns entry retrieval is skipped. Also ipv4 & ipv6 compatibility checks are skipped then.

## v0.1.0
- initial release
- added github actions release workflow
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ In addition to the library, it also contains a cli-wrapper around it.

```sh
webscan google.com -a
webscan 192.168.0.1 -a
```

## Installation
Expand All @@ -33,6 +34,7 @@ rm webscan_linux_amd64 webscan_linux_amd64.sha256
### DNS
Display dns information about the provided URL, and give improvement recommendations.

- [x] This is skipped if the input is an ipv4 or ipv6 address
- [x] Check who is the owner of the Domain via RDAP (not supported for country-TLDs)
- [ ] Check who is the owner of the DNS zone (== nameserver owner) - recursively
- [ ] Follow CNAMEs
Expand Down
8 changes: 4 additions & 4 deletions pkg/dnsScan/CheckSpf.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (engine Engine) CheckSpf() string {
// qnum = 0 - 255
// ip4-cidr-length = "/" ("0" / %x31-39 0*1DIGIT) ; value range 0-32
_, _, err := net.ParseCIDR(word)
if err != nil || !isIpv4(word) { // Check if ip is really ipv4, not ipv6
if err != nil || !IsIpv4(word) { // Check if ip is really ipv4, not ipv6
log.Fatalln("Invalid IPv4 address / cidr-range in SPF record:", word)
}
// It is not permitted to omit parts of the IP address instead of using CIDR notations. That is, use 192.0.2.0/24 instead of 192.0.2.
Expand All @@ -130,7 +130,7 @@ func (engine Engine) CheckSpf() string {
// qnum = 0 - 255
// If ip4-cidr-length is omitted, it is taken to be "/32".
parsedIp := net.ParseIP(word)
if parsedIp == nil || !isIpv4(word) { // Check if ip is really ipv4, not ipv6
if parsedIp == nil || !IsIpv4(word) { // Check if ip is really ipv4, not ipv6
log.Fatalln("Invalid IPv4 address in SPF record:", word)
}
}
Expand All @@ -148,7 +148,7 @@ func (engine Engine) CheckSpf() string {
// ip6-network = <as per Section 2.2 of [RFC4291]>
// ip6-cidr-length = "/" ("0" / %x31-39 0*2DIGIT) ; value range 0-128
_, _, err := net.ParseCIDR(word)
if err != nil || isIpv4(word) {
if err != nil || IsIpv4(word) {
log.Fatalln("Invalid IPv6 address / cidr-range in SPF record:", word)
}
// It is not permitted to omit parts of the IP address instead of using CIDR notations. That is, use 192.0.2.0/24 instead of 192.0.2.
Expand All @@ -157,7 +157,7 @@ func (engine Engine) CheckSpf() string {
// ip6-network = <as per Section 2.2 of [RFC4291]>
// If ip6-cidr-length is omitted, it is taken to be "/128".
parsedIp := net.ParseIP(word)
if parsedIp == nil || isIpv4(word) { // Check if ip is really ipv6, not ipv4
if parsedIp == nil || IsIpv4(word) { // Check if ip is really ipv6, not ipv4
log.Fatalln("Invalid IPv6 address in SPF record:", word)
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/dnsScan/isIpv4.go → pkg/dnsScan/IsIpv4.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ package dnsScan

import "strings"

func isIpv4(ip string) bool {
func IsIpv4(ip string) bool {
return strings.Count(ip, ":") < 2 // Explanation why this is accurate at https://stackoverflow.com/a/48519490
}
34 changes: 34 additions & 0 deletions pkg/tlsScan/ValidateTlsCertificate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package tlsScan

import (
"errors"
"net/http"
"os"
"time"
)

// checks whether the certificate is valid
func ValidateTlsCertificate(url string) error {
var (
err error
)

client := &http.Client{
Timeout: 5 * time.Second, // TODO 5s might be a bit long?
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}, // Don't follow redirects
}

_, err = client.Get("https://" + url)

if err != nil {
if os.IsTimeout(err) { // If err is timeout, tell user about it
return errors.New("http call exceeded 5s timeout")
} else {
return errors.New("Invalid TLS certificate used for HTTPS: " + errors.Unwrap(errors.Unwrap(err)).Error())
}
}

return nil
}
11 changes: 11 additions & 0 deletions pkg/webscan/Engine.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package webscan

import (
"net"
"net/http"

"github.com/thetillhoff/webscan/pkg/dnsScan"
Expand Down Expand Up @@ -39,6 +40,7 @@ type Engine struct {
httpRedirectLocation string
httpsStatusCode int
httpsRedirectLocation string
tlsResult error
tlsCiphers []tlsScan.TlsCipher
httpVersions []string
httpsVersions []string
Expand Down Expand Up @@ -68,6 +70,15 @@ func DefaultEngine(inputUrl string, dnsServer string) Engine {
dnsScanEngine = dnsScan.DefaultEngine()
}

netIP := net.ParseIP(inputUrl) // Try to parse inputUrl as IPaddress
if netIP != nil { // If inputUrl is IPaddress
if dnsScan.IsIpv4(inputUrl) { // If it's an ipv4 address
dnsScanEngine.ARecords = append(dnsScanEngine.ARecords, inputUrl) // Add as ipv4 address
} else { // If it's an ipv6 address
dnsScanEngine.AAAARecords = append(dnsScanEngine.AAAARecords, inputUrl) // Add as ipv6 address
}
}

return Engine{
url: inputUrl,

Expand Down
12 changes: 6 additions & 6 deletions pkg/webscan/PrintProtocolScanResults.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ func (engine Engine) PrintProtocolScanResults() {

if engine.HttpProtocolScan {

if engine.httpRedirectLocation != "" { // If http does redirect
if engine.isAvailableViaHttp && engine.httpRedirectLocation != "" { // If http does redirect
if !linebreakPrinted {
fmt.Println()
linebreakPrinted = true
}
fmt.Println("HTTP traffic is redirected to", engine.httpRedirectLocation) // Display redirection location
}
if engine.httpsRedirectLocation != "" { // If https does redirect
if engine.isAvailableViaHttps && engine.httpsRedirectLocation != "" { // If https does redirect
if !linebreakPrinted {
fmt.Println()
linebreakPrinted = true
Expand All @@ -29,15 +29,15 @@ func (engine Engine) PrintProtocolScanResults() {
}

// 301 & 308 are permanent redirects, 302, 303, 307 are temporary redirects, 300 and 304 are special cases are not meant for normal redirects
if engine.httpStatusCode != 301 && engine.httpStatusCode != 302 && engine.httpStatusCode != 303 && engine.httpStatusCode != 307 && engine.httpStatusCode != 308 { // Check against existing redirect status codes
if engine.isAvailableViaHttp && engine.httpStatusCode != 301 && engine.httpStatusCode != 302 && engine.httpStatusCode != 303 && engine.httpStatusCode != 307 && engine.httpStatusCode != 308 { // Check against existing redirect status codes
if !linebreakPrinted {
fmt.Println()
linebreakPrinted = true
}
fmt.Println("HTTP should only be used to redirect to an HTTPS location with a 301 or 308 status code. Got " + strconv.Itoa(engine.httpStatusCode))
}

if engine.httpsStatusCode != 200 {
if engine.isAvailableViaHttps && engine.httpsStatusCode != 200 {
if !linebreakPrinted {
fmt.Println()
linebreakPrinted = true
Expand Down Expand Up @@ -65,15 +65,15 @@ func (engine Engine) PrintProtocolScanResults() {
}
} else { // Else https serves a page

if engine.httpStatusCode != 301 && engine.httpStatusCode != 308 { // Http should redirect to https with 301 or 308
if engine.isAvailableViaHttp && engine.httpStatusCode != 301 && engine.httpStatusCode != 308 { // Http should redirect to https with 301 or 308
if !linebreakPrinted {
fmt.Println()
linebreakPrinted = true
}
fmt.Println("HTTP redirect to HTTPS should happen with 301 or 308 status code. Instead got: " + strconv.Itoa(engine.httpStatusCode))
}

if strings.TrimSuffix(engine.httpRedirectLocation, "/") != "https://"+engine.url { // http does not redirect to https (same origin)
if engine.isAvailableViaHttp && strings.TrimSuffix(engine.httpRedirectLocation, "/") != "https://"+engine.url { // http does not redirect to https (same origin)
if !linebreakPrinted {
fmt.Println()
linebreakPrinted = true
Expand Down
6 changes: 4 additions & 2 deletions pkg/webscan/PrintScanResults.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package webscan

func (engine Engine) PrintScanResults() {

engine.PrintDnsScanResults()
if len(engine.DnsScanEngine.ARecords) == 0 && len(engine.DnsScanEngine.AAAARecords) == 0 { // If input was an IPaddress, don't even try...
engine.PrintDnsScanResults()

engine.PrintIpScanResults()
engine.PrintIpScanResults()
}

engine.PrintPortScanResults()

Expand Down
7 changes: 7 additions & 0 deletions pkg/webscan/PrintTlsScanResults.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package webscan

import (
"fmt"

"github.com/thetillhoff/webscan/pkg/tlsScan"
)

func (engine Engine) PrintTlsScanResults() {

if engine.tlsResult != nil {
fmt.Println()
fmt.Println(engine.tlsResult)
}

if engine.TlsScan && engine.tlsCiphers != nil {
tlsScan.PrintRecommendations(engine.tlsCiphers)
}
Expand Down
21 changes: 12 additions & 9 deletions pkg/webscan/Scan.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package webscan

import (
"crypto/tls"
"net/http"
"time"
)
Expand All @@ -14,15 +15,17 @@ func (engine Engine) Scan() (Engine, error) {
request *http.Request
)

if engine.DetailedDnsScan {
engine, err = engine.ScanDnsDetailed()
if err != nil {
return engine, err
}
} else {
engine, err = engine.ScanDnsSimple()
if err != nil {
return engine, err
if len(engine.DnsScanEngine.ARecords) == 0 && len(engine.DnsScanEngine.AAAARecords) == 0 { // Only scan dns if input is a domain, not an ip address
if engine.DetailedDnsScan {
engine, err = engine.ScanDnsDetailed()
if err != nil {
return engine, err
}
} else {
engine, err = engine.ScanDnsSimple()
if err != nil {
return engine, err
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion pkg/webscan/ScanHttpProtocols.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package webscan

import protocolScan "github.com/thetillhoff/webscan/pkg/protocolScan"
import (
protocolScan "github.com/thetillhoff/webscan/pkg/protocolScan"
)

func (engine Engine) ScanHttpProtocols() (Engine, error) {
var (
Expand Down
1 change: 1 addition & 0 deletions pkg/webscan/ScanTls.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
func (engine Engine) ScanTls() (Engine, error) {

if engine.isAvailableViaHttps {
engine.tlsResult = tlsScan.ValidateTlsCertificate(engine.url)
engine.tlsCiphers = tlsScan.GetAvailableTlsCiphers(engine.url)
} // else there is no TLS to be scanned

Expand Down

0 comments on commit cb62463

Please sign in to comment.