aql

package
v0.0.0-...-5584581 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 24, 2025 License: AGPL-3.0 Imports: 21 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	DefaultQuerySettings = QueryDefinition{
		MaxDepth: 99,
	}
)
View Source
var Keywords []string // The keyword tokens
View Source
var Lexer *lexmachine.Lexer // The lexer object. Use this to construct a Scanner
View Source
var Literals []string // The tokens representing literal strings
View Source
var (
	PredefinedQueries = []QueryDefinition{
		{
			Name:        "High Value Targets",
			Query:       "ACYCLIC start:(tag=hvt)<-[]{1,10}-end:(&(dataLoader=Active Directory)(|(&(tag=account_enabled)(type=Person))(type=Group)))",
			Description: "High value targets in the network, that will compromise the entire AD if they're reached. This is the query to rule them all.",
			Default:     true,
		},
		{
			Name:        "Reach Domain Admin, Administrators and Enterprise Admins",
			Query:       "ACYCLIC start:(&(dataLoader=Active Directory)(type=Group)(|(objectSid=S-1-5-32-544)(objectSid=S-1-5-21-*-512)(objectSid=S-1-5-21-*-519)))<-[]{1,10}-end:(|(type=Person)(type=Group))",
			Description: "Find all the paths to reach Domain Admins, Administrators and Enterprise Admins.",
			Category:    "Active Directory",
		},
		{
			Name:        "Run DCsync",
			Query:       "ACYCLIC start:(&(name=DCsync)(type=Callable-Service-Point))<-[]{1,10}-end:(|(type=Person)(type=Group))",
			Description: "Find all the paths to run DCsync, enabling the attacker to dump all the hashes from the domain.",
			Category:    "Active Directory",
		},
		{
			Name:        "Unconstrained Delegation",
			Query:       "ACYCLIC start:(tag=unconstrained)<-[]{1,10}-end:(|(type=Person)(type=Group))",
			Description: "How to reach machines that have computer accounts with unconstrained delegation (non-DCs)",
		},
		{
			Name:     "What can accounts with no Kerberos preauth requirement reach? (ASREPROAST)",
			Query:    "ACYCLIC start:(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=4194304)(tag=account_active))-[]{1,10}->end:()",
			Category: "Active Directory",
		},
		{
			Name:     "Who can pwn your AD by sideloading a custom DLL on your DC? (Old DCs only)",
			Query:    "ACYCLIC start:(distinguishedname=CN=MicrosoftDNS,CN=System,DC=*)<-[]{1,15}-end:(|(type=Person)(type=Group))",
			Category: "Active Directory",
		},
		{
			Name:     "Who can dump SAM/SYSTEM or your ntds.dit remotely or via RDP? (Server and Backup Operators)",
			Query:    "ACYCLIC start:(&(dataLoader=Active Directory)(|(objectSid=S-1-5-32-551)(objectSid=S-1-5-32-549)))<-[]{1,10}-end:()",
			Category: "Active Directory",
		},
		{
			Name:     "Enroll in ESC1 vulnerable certificate templates (client auth + pose as anyone)",
			Query:    "ACYCLIC start:(&(type=PKI-Certificate-Template)(msPKI-Certificate-Name-Flag:and:=1)(|(pKIExtendedKeyUsage=1.3.6.1.5.5.7.3.2)(pKIExtendedKeyUsage=1.3.5.1.5.2.3.4)(pKIExtendedKeyUsage=1.3.6.1.4.1.311.20.2.2)(pKIExtendedKeyUsage=2.5.29.37.0)(pKIExtendedKeyUsage:count:=0)))<-[CertificateEnroll]-()-[]{1,10}-end:(|(type=Person)(type=Group))",
			Category: "Certificate Services",
		},
		{
			Name:     "Enroll in ESC15 vulnerable certificate templates (v1 + pose as anyone)",
			Query:    "ACYCLIC start:(&(type=PKI-Certificate-Template)(msPKI-Certificate-Name-Flag:and:=1)(msPKI-Template-Schema-Version=1))<-[CertificateEnroll]-()-[]{1,10}-end:(|(type=Person)(type=Group))",
			Category: "Certificate Services",
		},
		{
			Name:     "What can Domain Users, Authenticated Users and Everyone do?",
			Query:    "ACYCLIC start:(&(dataLoader=Active Directory)(|(objectSid=S-1-5-21-*-513)(objectSid=S-1-5-11)(objectSid=S-1-1-0)))-[]{1,10}->end:()",
			Category: "Active Directory",
		},
		{
			Name:     "Who can dump a virtual DC? (hypervisor/SAN sounding groups)",
			Query:    "ACYCLIC start:(&(dataLoader=Active Directory)(type=Group)(|(name=*vcenter*)(name=*vmware*)(name=*esxi*)(name=*vsan*)(name=*simplivity*)))<-[]{1,10}-end:()",
			Category: "Active Directory",
		},
		{
			Name:     "Who can wipe or access your backups? (backup sounding groups)",
			Query:    "ACYCLIC start:(&(dataLoader=Active Directory)(type=Group)(|(name=*backup*)(name=*veeam*)(name=*tsm*)(name=*tivoli storage*)(name=*rubrik*)(name=*commvault*)))<-[]{1,10}-end:(|(type=Person)(type=Group))",
			Category: "Active Directory",
		},
		{
			Name:     "Who can change GPOs?",
			Query:    "ACYCLIC start:(&(dataLoader=Active Directory)(type=Group-Policy-Container))<-[]{1,10}-end:(|(type=Person)(type=Group))",
			Category: "Active Directory",
		},
		{
			Name:     "What can users not required to have a password reach?",
			Query:    "ACYCLIC start:(&(dataLoader=Active Directory)(type=Person)(userAccountControl:1.2.840.113556.1.4.803:=32))-[]{1,10}->end:()",
			Category: "Active Directory",
		},
		{
			Name:     "What can users that can't change password reach?",
			Query:    "ACYCLIC start:(&(type=Person)(userAccountControl:1.2.840.113556.1.4.803:=64))-[]{1,10}->end:()",
			Category: "Active Directory",
		},
		{
			Name:     "What can users with never expiring passwords reach?",
			Query:    "ACYCLIC start:(&(type=Person)(userAccountControl:1.2.840.113556.1.4.803:=65536))-[]{1,10}->end:()",
			Category: "Active Directory",
		},
		{
			Name:     "What can accounts that have a password older than 5 years reach?",
			Query:    "ACYCLIC start:(&(objectClass=Person)(!(pwdLastSet=0))(pwdLastSet:since:<-5Y)(!(userAccountControl:and:=2)))-[]{1,10}->end:()",
			Category: "Active Directory",
		},
		{
			Name:     "What can accounts that have never set a password reach?",
			Query:    "ACYCLIC start:(&(dataLoader=Active Directory)(objectClass=Person)(pwdLastSet=0)(|(logonCount=0)(!(logonCount=*)))(!(userAccountControl:and:=2)))-[]{1,10}->end:()",
			Category: "Active Directory",
		},
		{
			Name:        "Protected Users",
			Query:       "ACYCLIC start:(&(type=Group)(distinguishedName=CN=Protected Users,*))<-[]{1,10}-end:(|(type=Person)(type=Group))",
			Description: "Who can tamper with the Protected Users group?",
			Category:    "Active Directory",
		},
		{
			Name:     "What can kerberoastable user accounts reach? (all encryption types)",
			Query:    "ACYCLIC start:(&(type=Person)(servicePrincipalName=*)(tag=account_active))<-[]{1,10}-end:(|(type=Person)(type=Group))",
			Category: "Roasting",
		},
		{
			Name:     "What can kerberoastable user accounts reach? (RC4 encryption)",
			Query:    "ACYCLIC start:(&(type=Person)(servicePrincipalName=*)(|(msDS-SupportedEncryptionTypes:and:=0x4)(!msDS-SupportedEncryptionTypes=*))(tag=account_active))<-[]{1,10}-end:(|(type=Person)(type=Group))",
			Category: "Roasting",
		},
		{
			Name:        "Large groups",
			Query:       "ACYCLIC start:(&(type=Group)(member:count:>100))-[]{1,10}->end:()",
			Description: "What can large groups (more than 100 members) reach?",
			Category:    "Examples",
		},
		{
			Name:        "Domain Controllers",
			Query:       "ACYCLIC start:(&(type=Machine)(out=MachineAccount,(&(type=Computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))))<-[]{1,10}-end:(|(type=Person)(type=Group))",
			Description: "Domain Controllers are critical servers in Active Directory environments. They authenticate users and services within the domain. Compromising these machines allows attackers to take over the entire AD.",
			Category:    "Active Directory",
		},
		{
			Name:        "Who can reach Read-Only Domain Controllers (RODC)",
			Query:       "ACYCLIC start:(&(type=Machine)(out=MachineAccount,(&(type=Computer)(primaryGroupId=521))))<-[]{1,10}-end:(|(type=Person)(type=Group))",
			Description: "Read-Only Domain Controllers (RODC) are used to reduce the risk of password replication. They only replicate a subset of the domain's data and are typically located in remote offices. Compromising RODCs can lead to unauthorized access to sensitive information, depending on how they're configured for caching credentials.",
			Category:    "Active Directory",
		},
		{
			Name:     "Computers with unconstrained delegation (non DCs)?",
			Query:    "ACYCLIC start:(&(type=Computer)(userAccountControl:1.2.840.113556.1.4.803:=524288)(!userAccountControl:1.2.840.113556.1.4.803:=8192))<-[]{1,10}-end:(|(type=Person)(type=Group))",
			Category: "Active Directory",
		},
		{
			Name:     "Computers with constrained delegation (non DCs)",
			Query:    "ACYCLIC start:(&(objectCategory=computer)(msds-allowedtodelegateto=*)(!userAccountControl:1.2.840.113556.1.4.803:=8192))<-[]{1,10}-end:(|(type=Person)(type=Group))",
			Category: "Active Directory",
		},
		{
			Name:     "Users that are members of more than 25 groups",
			Query:    "ACYCLIC start:(&(type=Person)(memberOf:count:>10))",
			Category: "Examples",
		},
		{
			Name:     "100 random machines",
			Query:    "ACYCLIC start:(&(type=Machine)(out=MachineAccount,(userAccountControl:1.2.840.113556.1.4.803:=4096))) LIMIT 100",
			Category: "Examples",
		},
	}
)
View Source
var StaticLexers = map[string]TokenID{
	"\\(": LParan,
	"\\)": RParan,

	"\\[": LBracket,
	"\\]": RBracket,

	"\\{": LBrace,
	"\\}": RBrace,

	"\\~":    Tilde,
	"\\=":    Equals,
	"\\<":    LessThan,
	"\\<\\=": LessThanEquals,
	"\\>":    GreaterThan,
	"\\>\\=": GreaterThanEquals,

	"\\&": BinaryAnd,
	"\\|": BinaryOr,
	"\\^": BinaryNot,

	"AND": And,
	"OR":  Or,
	"NOT": Not,

	"TRUE":  True,
	"FALSE": False,

	"\\*":    Star,
	"\\/":    Slash,
	"\\!":    Exclamation,
	"\\.":    Dot,
	"\\.\\.": Dotdot,
	"\\,":    Comma,

	"\\:":    Colon,
	"\\-":    EdgeAnyDirection,
	"\\-\\>": EdgeOut,
	"\\<\\-": EdgeIn,

	"MATCH":    Match,
	"IS":       Is,
	"WHERE":    Where,
	"SKIP":     Skip,
	"OFFSET":   Offset,
	"LIMIT":    Limit,
	"ORDER BY": OrderBy,
	"DESC":     Desc,
	"UNION":    Union,

	`//[^\n]*\n?`: Comment,
	`/\*([^*]|\r|\n|(\*+([^*/]|\r|\n)))*\*+/`: Comment,
	`([a-zA-Z]|_)([a-zA-Z0-9]|_|-)*`:          Identifier,
	`\\#([a-zA-Z]|_)([a-zA-Z0-9]|_)+`:         HashIdentifier,
	`\\@([a-zA-Z]|_)([a-zA-Z0-9]|_)+`:         AtIdentifier,

	`( |\t|\n|\r)+`: Whitespace,
}
View Source
var TokenIds map[string]int // A map from the token names to their int ids
View Source
var Tokens []string // All of the tokens (including literals and keywords)

Functions

func GetComparator

func GetComparator(ts *TokenStream) (query.ComparatorType, error)

func ParseObjectTypeStrings

func ParseObjectTypeStrings(typeslice []string) (map[engine.ObjectType]struct{}, error)

func ResolveWithOptions

func ResolveWithOptions(resolver AQLresolver, opts ResolverOptions) (*graph.Graph[*engine.Object, engine.EdgeBitmap], error)

func TokenIDStrings

func TokenIDStrings() []string

TokenIDStrings returns a slice of all String values of the enum

Types

type AQLquery

type AQLquery struct {
	Sources []NodeQuery // count is n

	Next               []EdgeSearcher // count is n-1
	Mode               QueryMode
	Shortest           bool
	OverAllProbability engine.Probability
	// contains filtered or unexported fields
}

func (AQLquery) Resolve

type AQLqueryUnion

type AQLqueryUnion struct {
	// contains filtered or unexported fields
}

Union of multiple queries

func (AQLqueryUnion) Resolve

type AQLresolver

type AQLresolver interface {
	Resolve(ResolverOptions) (*graph.Graph[*engine.Object, engine.EdgeBitmap], error)
}

func ParseAQLQuery

func ParseAQLQuery(s string, ao *engine.Objects) (AQLresolver, error)

type EdgeMatcher

type EdgeMatcher struct {
	Bitmap      engine.EdgeBitmap
	Count       int64 // minimum number of edges to match
	Comparator  query.ComparatorType
	NoTrimEdges bool // don't trim edges to just the filter
}

type EdgeSearcher

type EdgeSearcher struct {
	PathNodeRequirement *NodeQuery // Nodes passed along the way must fulfill this filter

	FilterEdges                  EdgeMatcher // match any of these
	Direction                    engine.EdgeDirection
	MinIterations, MaxIterations int // there should be between min and max iterations in the chain
	ProbabilityValue             engine.Probability
	ProbabilityComparator        query.ComparatorType
	// contains filtered or unexported fields
}

type FirstLimiter

type FirstLimiter int

func (FirstLimiter) Limit

type IndexLookup

type IndexLookup struct {
	// contains filtered or unexported fields
}

type NodeLimiter

type NodeLimiter interface {
	Limit(engine.ObjectSlice) engine.ObjectSlice
}

type NodeMatcher

type NodeMatcher interface {
	Match(o *engine.Object) bool
}

type NodeQuery

type NodeQuery struct {
	IndexLookup IndexLookup      // Possible start of search, quickly narrows it down
	Selector    query.NodeFilter // Where style boolean approval filter for objects
	OrderBy     NodeSorter       // Sorting
	Reference   string           // For cross result reference
	Skip        int              // Skipping
	Limit       int              // Limiting
}

func (NodeQuery) Populate

func (nq NodeQuery) Populate(ao *engine.Objects) *engine.Objects

type NodeSorter

type NodeSorter interface {
	Sort(engine.ObjectSlice) engine.ObjectSlice
}

type NodeSorterImpl

type NodeSorterImpl struct {
	Attr       engine.Attribute
	Descending bool
}

func (NodeSorterImpl) Sort

type QueryDefinition

type QueryDefinition struct {
	Name        string `json:"name"`
	Query       string `json:"query,omitempty"`
	Category    string `json:"category,omitempty"` // Optional category for grouping queries in UI
	Description string `json:"description,omitempty"`

	MaxDepth                  int                `json:"max_depth,omitempty"`
	MaxOutgoingConnections    int                `json:"max_outgoing_connections,omitempty"`
	Default                   bool               `json:"default,omitempty"`
	MinAccumulatedProbability engine.Probability `json:"min_accumulated_probability,omitempty"`

	UserDefined bool `json:"user_defined,omitempty"`
}

Can return built in queries and user defined persisted queries

func DefaultQueryDefinition

func DefaultQueryDefinition() QueryDefinition

func ParseQueryDefinitionFromPOST

func ParseQueryDefinitionFromPOST(ctx *gin.Context) (QueryDefinition, error)

func (QueryDefinition) ID

func (q QueryDefinition) ID() string

func (QueryDefinition) Resolver

func (qd QueryDefinition) Resolver(ao *engine.Objects) (AQLresolver, error)

Compiles the query and makes it a resolver

type QueryMode

type QueryMode int
const (
	Walk    QueryMode = iota // No Homomorphism
	Trail                    // Edge homomorphism (unique edges)
	Acyclic                  // Node homomorphism (unique nodes)
	Simple                   // Partial node-isomorphism
)

type ResolverOptions

type ResolverOptions struct {
	MaxDepth                  int                `json:"max_depth,omitempty"`
	MaxOutgoingConnections    int                `json:"max_outgoing_connections,omitempty"`
	MinEdgeProbability        engine.Probability `json:"min_edge_probability,omitempty"`
	MinAccumulatedProbability engine.Probability `json:"min_accumulated_probability,omitempty"`
	PruneIslands              bool               `json:"prune_islands,omitempty"`
	NodeLimit                 int                `json:"nodelimit,omitempty"`
}

func NewResolverOptions

func NewResolverOptions() ResolverOptions

type SkipLimiter

type SkipLimiter int

func (SkipLimiter) Limit

type Token

type Token struct {
	Native   any
	Value    string
	Position machines.Match
	Type     TokenID
}

func (Token) Is

func (t Token) Is(id TokenID) bool

func (Token) String

func (t Token) String() string

type TokenID

type TokenID int
const (
	Invalid TokenID = iota

	Star
	Slash
	Exclamation

	Dot
	Dotdot
	Comma
	Colon
	Equals
	Tilde
	LessThan
	LessThanEquals
	GreaterThan
	GreaterThanEquals

	And
	Or
	Xor
	Not

	BinaryAnd
	BinaryOr
	BinaryNot

	LParan // (
	RParan // )

	LBracket // [
	RBracket // ]

	LBrace // {
	RBrace // }

	EdgeAnyDirection // -
	EdgeIn           // <-
	EdgeOut          // ->

	Is
	Match
	Where
	Skip
	Offset
	Limit
	OrderBy
	Desc
	Union

	True
	False

	Literal
	Keyword

	Whitespace

	Integer
	Float

	UnquotedLDAPString
	QuotedString // Quoted string

	Identifier
	HashIdentifier
	AtIdentifier

	Comment

	MAXTOKEN = Comment
)

func TokenIDString

func TokenIDString(s string) (TokenID, error)

TokenIDString retrieves an enum value from the enum constants string name. Throws an error if the param is not part of the enum.

func TokenIDValues

func TokenIDValues() []TokenID

TokenIDValues returns all values of the enum

func (TokenID) IsATokenID

func (i TokenID) IsATokenID() bool

IsATokenID returns "true" if the value is listed in the enum definition. "false" otherwise

func (TokenID) String

func (i TokenID) String() string

type TokenStream

type TokenStream struct {
	// contains filtered or unexported fields
}

func Parse

func Parse(input string) (*TokenStream, error)

func (*TokenStream) EOF

func (ts *TokenStream) EOF() bool

func (*TokenStream) Next

func (ts *TokenStream) Next() bool

func (*TokenStream) NextIfIs

func (ts *TokenStream) NextIfIs(id TokenID) bool

func (*TokenStream) PeekNextRawToken

func (ts *TokenStream) PeekNextRawToken() Token

func (*TokenStream) PeekNextToken

func (ts *TokenStream) PeekNextToken() Token

func (*TokenStream) Prev

func (ts *TokenStream) Prev() bool

func (*TokenStream) SnarfTextUntil

func (ts *TokenStream) SnarfTextUntil(id TokenID) string

func (*TokenStream) Token

func (ts *TokenStream) Token() Token

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL