forked from zmap/zgrab2
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
RicYaben
committed
Oct 17, 2024
1 parent
735e746
commit 4b736d3
Showing
72 changed files
with
739 additions
and
3 deletions.
There are no files selected for viewing
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package modules | ||
|
||
import "github.com/zmap/zgrab2/modules/opcua" | ||
|
||
func init() { | ||
opcua.RegisterModule() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
package opcua | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/gopcua/opcua" | ||
"github.com/gopcua/opcua/id" | ||
"github.com/gopcua/opcua/ua" | ||
"github.com/pkg/errors" | ||
"github.com/zmap/zgrab2" | ||
) | ||
|
||
type NodeDef struct { | ||
NodeID *ua.NodeID `json:"nodeID"` | ||
NodeClass ua.NodeClass `json:"nodeClass"` | ||
BrowseName string `json:"browseName"` | ||
Description string `json:"description"` | ||
AccessLevel ua.AccessLevelType `json:"accessLevel"` | ||
Path string `json:"path"` | ||
DataType string `json:"dataType"` | ||
Writable bool `json:"writable"` | ||
} | ||
|
||
func (n *NodeDef) SetNodeClass(v *ua.DataValue) error { | ||
switch err := v.Status; err { | ||
case ua.StatusOK: | ||
n.NodeClass = ua.NodeClass(v.Value.Int()) | ||
return nil | ||
default: | ||
return err | ||
} | ||
} | ||
|
||
func (n *NodeDef) SetBrowseName(v *ua.DataValue) error { | ||
switch err := v.Status; err { | ||
case ua.StatusOK: | ||
n.BrowseName = v.Value.String() | ||
return nil | ||
default: | ||
return err | ||
} | ||
} | ||
|
||
func (n *NodeDef) SetDescription(v *ua.DataValue) error { | ||
switch err := v.Status; err { | ||
case ua.StatusOK: | ||
if v.Value != nil { | ||
n.Description = v.Value.String() | ||
} | ||
return nil | ||
case ua.StatusBadAttributeIDInvalid: | ||
return nil | ||
default: | ||
return err | ||
} | ||
} | ||
|
||
func (n *NodeDef) SetAccessLevel(v *ua.DataValue) error { | ||
switch err := v.Status; err { | ||
case ua.StatusOK: | ||
n.AccessLevel = ua.AccessLevelType(v.Value.Int()) | ||
n.Writable = n.AccessLevel&ua.AccessLevelTypeCurrentWrite == ua.AccessLevelTypeCurrentWrite | ||
return nil | ||
case ua.StatusBadAttributeIDInvalid: | ||
return nil | ||
default: | ||
return err | ||
} | ||
} | ||
|
||
func (n *NodeDef) SetDataType(dt *ua.DataValue) error { | ||
switch err := dt.Status; err { | ||
case ua.StatusOK: | ||
switch v := dt.Value.NodeID().IntID(); v { | ||
case id.DateTime: | ||
n.DataType = "time.Time" | ||
case id.Boolean: | ||
n.DataType = "bool" | ||
case id.SByte: | ||
n.DataType = "int8" | ||
case id.Int16: | ||
n.DataType = "int16" | ||
case id.Int32: | ||
n.DataType = "int32" | ||
case id.Byte: | ||
n.DataType = "byte" | ||
case id.UInt16: | ||
n.DataType = "uint16" | ||
case id.UInt32: | ||
n.DataType = "uint32" | ||
case id.UtcTime: | ||
n.DataType = "time.Time" | ||
case id.String: | ||
n.DataType = "string" | ||
case id.Float: | ||
n.DataType = "float32" | ||
case id.Double: | ||
n.DataType = "float64" | ||
default: | ||
n.DataType = dt.Value.NodeID().String() | ||
} | ||
return nil | ||
case ua.StatusBadAttributeIDInvalid: | ||
return nil | ||
default: | ||
return err | ||
} | ||
} | ||
|
||
type EndpointResult struct { | ||
Error *zgrab2.ScanError `json:"error,omitempty"` | ||
EndpointDescription *ua.EndpointDescription | ||
Authenticated []string `json:"authentication"` | ||
Endpoint map[string]interface{} `json:"endpoint"` | ||
Nodes []*NodeDef `json:"nodes"` | ||
Namespaces []string `json:"namespaces"` | ||
} | ||
|
||
func newEndpoint(ep *ua.EndpointDescription) *EndpointResult { | ||
return &EndpointResult{ | ||
EndpointDescription: ep, | ||
Authenticated: []string{}, | ||
} | ||
} | ||
|
||
type Results struct { | ||
Endpoints []*EndpointResult `json:"endpoints,omitempty"` | ||
} | ||
|
||
type browser struct { | ||
level uint | ||
maxLevel uint | ||
ctx context.Context | ||
} | ||
|
||
func newBrowser(level uint, ctx context.Context) *browser { | ||
b := &browser{ | ||
ctx: ctx, | ||
maxLevel: 10, | ||
} | ||
b.setLevel(level) | ||
|
||
return b | ||
} | ||
|
||
func (b *browser) setLevel(level uint) { | ||
if level > b.maxLevel { | ||
panic(errors.Errorf("failed to set browser level. The maximum level is %d, but attempted to set %d", b.maxLevel, level)) | ||
} | ||
b.level = level | ||
} | ||
|
||
func (b *browser) Node(n *opcua.Node) (*NodeDef, error) { | ||
var def = &NodeDef{ | ||
NodeID: n.ID, | ||
} | ||
|
||
// NOTE: We do not need to know anything else about the node | ||
// If you are modifying this, please be aware of possible | ||
// ethical issues. Reading values from nodes will not offer | ||
// more insights. | ||
attrs, err := n.Attributes( | ||
b.ctx, | ||
ua.AttributeIDNodeClass, | ||
ua.AttributeIDBrowseName, | ||
ua.AttributeIDDescription, | ||
ua.AttributeIDAccessLevel, | ||
ua.AttributeIDDataType, | ||
) | ||
if err != nil { | ||
return def, err | ||
} | ||
|
||
for i, f := range []func(v *ua.DataValue) error{ | ||
def.SetNodeClass, | ||
def.SetBrowseName, | ||
def.SetDescription, | ||
def.SetAccessLevel, | ||
def.SetDataType, | ||
} { | ||
if err := f(attrs[i]); err != nil { | ||
return def, err | ||
} | ||
} | ||
|
||
return def, nil | ||
} | ||
|
||
func (b *browser) getChildren(refType uint32, n *opcua.Node, path string, level int) ([]*NodeDef, error) { | ||
refs, err := n.ReferencedNodes(b.ctx, refType, ua.BrowseDirectionForward, ua.NodeClassAll, true) | ||
if err != nil { | ||
return nil, errors.Errorf("References: %d: %s", refType, err) | ||
} | ||
|
||
var nodes []*NodeDef | ||
for _, rn := range refs { | ||
children, err := b.browse(rn, path, level+1) | ||
if err != nil { | ||
return nil, errors.Errorf("browse children: %s", err) | ||
} | ||
nodes = append(nodes, children...) | ||
} | ||
return nodes, nil | ||
} | ||
|
||
func (b *browser) browse(n *opcua.Node, path string, level int) ([]*NodeDef, error) { | ||
if level > int(b.level) { | ||
return nil, nil | ||
} | ||
|
||
def, err := b.Node(n) | ||
if err != nil { | ||
return nil, err | ||
} | ||
def.Path = join(path, def.BrowseName) | ||
|
||
var nodes []*NodeDef | ||
nodes = append(nodes, def) | ||
for _, refType := range []uint32{id.HasComponent, id.Organizes, id.HasProperty} { | ||
nChilds, err := b.getChildren(refType, n, def.Path, level) | ||
if err != nil { | ||
return nil, err | ||
} | ||
nodes = append(nodes, nChilds...) | ||
} | ||
return nodes, nil | ||
} | ||
|
||
func join(a, b string) string { | ||
if a == "" { | ||
return b | ||
} | ||
return a + "." + b | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package opcua | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/zmap/zgrab2" | ||
) | ||
|
||
type opcuaTester struct { | ||
address string | ||
port uint | ||
uri string | ||
certHost string | ||
endpoint string | ||
level uint | ||
} | ||
|
||
func (cfg *opcuaTester) getScanner() (*Scanner, error) { | ||
var module Module | ||
flags := module.NewFlags().(*Flags) | ||
flags.Uri = cfg.uri | ||
flags.CertHost = cfg.certHost | ||
flags.Endpoint = cfg.endpoint | ||
flags.BrowseDepth = cfg.level | ||
|
||
scanner := module.NewScanner() | ||
if err := scanner.Init(flags); err != nil { | ||
return nil, err | ||
} | ||
return scanner.(*Scanner), nil | ||
} | ||
|
||
func (cfg *opcuaTester) runTest(t *testing.T, testName string) { | ||
target := zgrab2.ScanTarget{ | ||
Port: &cfg.port, | ||
Domain: cfg.address, | ||
} | ||
|
||
scanner, err := cfg.getScanner() | ||
if err != nil { | ||
t.Fatalf("[%s] Unexpected error: %v", testName, err) | ||
} | ||
|
||
st, res, err := scanner.Scan(target) | ||
if err != nil { | ||
t.Fatalf("[%s] error while scanning: %v, %v", testName, err, st) | ||
} | ||
t.Logf("%+v", res) | ||
} | ||
|
||
var tests = map[string]*opcuaTester{ | ||
"demo": { | ||
address: "opcua.demo-this.com", | ||
port: 51210, | ||
uri: "urn:opcua:scanner", | ||
certHost: "localhost", | ||
endpoint: "UA/SampleServer", | ||
level: 2, | ||
}, | ||
} | ||
|
||
func TestOPCUA(t *testing.T) { | ||
for tname, cfg := range tests { | ||
cfg.runTest(t, tname) | ||
} | ||
} |
Oops, something went wrong.