Skip to content

Commit dcca4db

Browse files
authored
Improve GitHub Actions workflows (#467)
1 parent 2d7441b commit dcca4db

File tree

16 files changed

+150
-188
lines changed

16 files changed

+150
-188
lines changed

.github/.yamllint

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ rules:
88
trailing-spaces:
99
level: error
1010
line-length:
11+
max: 1000
1112
level: warning
1213
new-lines:
1314
level: error

.github/workflows/validation.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import glob
2+
import os
3+
import sys
4+
from typing import List, Literal, Optional
5+
6+
import yaml
7+
from pydantic import BaseModel, HttpUrl, RootModel, ValidationError, constr, model_validator, field_validator, ConfigDict
8+
9+
# Disable datetime parsing
10+
yaml.SafeLoader.yaml_implicit_resolvers = {k: [r for r in v if r[0] != 'tag:yaml.org,2002:timestamp'] for k, v in yaml.SafeLoader.yaml_implicit_resolvers.items()}
11+
12+
13+
safe_str = constr(pattern=r'^([a-zA-Z0-9\s.,!?\'"():;\-\+_*#@/\\&%~=]|`[a-zA-Z0-9\s.,!?\'"():;\-\+_*#@/\\&<>%\{\}~=]+`|->)+$')
14+
15+
16+
class LolbasModel(BaseModel):
17+
model_config = ConfigDict(extra="forbid")
18+
19+
20+
class AliasItem(LolbasModel):
21+
Alias: Optional[str]
22+
23+
24+
class TagItem(RootModel[dict[constr(pattern=r'^[A-Z]'), str]]):
25+
pass
26+
27+
28+
class CommandItem(LolbasModel):
29+
Command: str
30+
Description: safe_str
31+
Usecase: safe_str
32+
Category: Literal['ADS', 'AWL Bypass', 'Compile', 'Conceal', 'Copy', 'Credentials', 'Decode', 'Download', 'Dump', 'Encode', 'Execute', 'Reconnaissance', 'Tamper', 'UAC Bypass', 'Upload']
33+
Privileges: str
34+
MitreID: constr(pattern=r'^T[0-9]{4}(\.[0-9]{3})?$')
35+
OperatingSystem: str
36+
Tags: Optional[List[TagItem]] = None
37+
38+
39+
class FullPathItem(LolbasModel):
40+
Path: constr(pattern=r'^(([cC]:)\\([a-zA-Z0-9\-\_\. \(\)<>]+\\)*([a-zA-Z0-9_\-\.]+\.[a-z0-9]{3})|no default)$')
41+
42+
43+
class CodeSampleItem(LolbasModel):
44+
Code: str
45+
46+
47+
class DetectionItem(LolbasModel):
48+
IOC: Optional[str] = None
49+
Sigma: Optional[HttpUrl] = None
50+
Analysis: Optional[HttpUrl] = None
51+
Elastic: Optional[HttpUrl] = None
52+
Splunk: Optional[HttpUrl] = None
53+
BlockRule: Optional[HttpUrl] = None
54+
55+
@model_validator(mode="after")
56+
def validate_exclusive_urls(cls, values):
57+
url_fields = ['IOC', 'Sigma', 'Analysis', 'Elastic', 'Splunk', 'BlockRule']
58+
present = [field for field in url_fields if values.__dict__.get(field) is not None]
59+
60+
if len(present) != 1:
61+
raise ValueError(f"Exactly one of the following must be provided: {url_fields}.", f"Currently set: {present or 'none'}")
62+
63+
return values
64+
65+
66+
class ResourceItem(LolbasModel):
67+
Link: HttpUrl
68+
69+
70+
class AcknowledgementItem(LolbasModel):
71+
Person: str
72+
Handle: Optional[constr(pattern=r'^(@(\w){1,15})?$')] = None
73+
74+
75+
class MainModel(LolbasModel):
76+
Name: str
77+
Description: safe_str
78+
Aliases: Optional[List[AliasItem]] = None
79+
Author: str
80+
Created: constr(pattern=r'\d{4}-\d{2}-\d{2}')
81+
Commands: List[CommandItem]
82+
Full_Path: List[FullPathItem]
83+
Code_Sample: Optional[List[CodeSampleItem]] = None
84+
Detection: Optional[List[DetectionItem]] = None
85+
Resources: Optional[List[ResourceItem]] = None
86+
Acknowledgement: Optional[List[AcknowledgementItem]] = None
87+
88+
89+
if __name__ == "__main__":
90+
def escaper(x): return x.replace('%', '%25').replace('\r', '%0D').replace('\n', '%0A')
91+
92+
yaml_files = glob.glob("yml/**", recursive=True)
93+
94+
if not yaml_files:
95+
print("No YAML files found under 'yml/**'.")
96+
sys.exit(-1)
97+
98+
has_errors = False
99+
for file_path in yaml_files:
100+
if os.path.isfile(file_path) and not file_path.startswith('yml/HonorableMentions/'):
101+
try:
102+
with open(file_path, 'r', encoding='utf-8') as f:
103+
data = yaml.safe_load(f)
104+
MainModel(**data)
105+
print(f"✅ Valid: {file_path}")
106+
except ValidationError as ve:
107+
print(f"❌ Validation error in {file_path}:\n{ve}\n")
108+
for err in ve.errors():
109+
# GitHub Actions error format
110+
print(err)
111+
path = '.'.join([str(x) for x in err.get('loc', [None])])
112+
msg = err.get('msg', 'Unknown validation error')
113+
print(f"::error file={file_path},line=1,title={escaper(err.get('type') or 'Validation error')}::{escaper(msg)}: {escaper(path)}")
114+
has_errors = True
115+
except Exception as e:
116+
print(f"⚠️ Error processing {file_path}: {e}\n")
117+
print(f"::error file={file_path},line=1,title=Processing error::Error processing file: {escaper(e)}")
118+
has_errors = True
119+
120+
sys.exit(-1 if has_errors else 0)

.github/workflows/yaml-linting.yml

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ jobs:
88
runs-on: ubuntu-latest
99
steps:
1010
- uses: actions/checkout@v3
11+
1112
- name: Check file extensions
1213
run: |
1314
files=$(find "$GITHUB_WORKSPACE/yml" -type f -not -name "*.yml");
@@ -17,6 +18,7 @@ jobs:
1718
exit 1;
1819
fi
1920
unset files
21+
2022
- name: Check duplicate file names
2123
run: |
2224
files=$(find "$GITHUB_WORKSPACE/yml/OSBinaries" "$GITHUB_WORKSPACE/yml/OtherMSBinaries" -type f -printf '%h %f\n' -iname "*.yml" | sort -t ' ' -k 2,2 -f | uniq -i -f 1 --all-repeated=separate | tr ' ' '/')
@@ -26,34 +28,12 @@ jobs:
2628
exit 1;
2729
fi
2830
unset files
29-
- name: yaml-lint
30-
uses: ibiqlik/action-yamllint@v3
31-
with:
32-
no_warnings: true
33-
file_or_dir: yml/**/*.yml
34-
config_file: .github/.yamllint
35-
- name: Validate Template Schema
36-
uses: cketti/[email protected]
37-
with:
38-
files: YML-Template.yml
39-
schema: YML-Schema.yml
40-
- name: Validate OSBinaries YAML Schema
41-
uses: cketti/[email protected]
42-
with:
43-
files: yml/OSBinaries/*.yml
44-
schema: YML-Schema.yml
45-
- name: Validate OSLibraries YAML Schema
46-
uses: cketti/[email protected]
47-
with:
48-
files: yml/OSLibraries/*.yml
49-
schema: YML-Schema.yml
50-
- name: Validate OSScripts YAML Schema
51-
uses: cketti/[email protected]
52-
with:
53-
files: yml/OSScripts/*.yml
54-
schema: YML-Schema.yml
55-
- name: Validate OtherMSBinaries YAML Schema
56-
uses: cketti/[email protected]
57-
with:
58-
files: yml/OtherMSBinaries/*.yml
59-
schema: YML-Schema.yml
31+
32+
- name: Install python dependencies
33+
run: pip install yamllint==1.37.1 pydantic==2.11.9
34+
35+
- name: Lint YAML files
36+
run: yamllint -c .github/.yamllint yml/**/
37+
38+
- name: Validate YAML schemas
39+
run: python3 .github/workflows/validation.py

YML-Schema.yml

Lines changed: 0 additions & 129 deletions
This file was deleted.

yml/OSBinaries/Bitsadmin.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ Commands:
3535
Full_Path:
3636
- Path: C:\Windows\System32\bitsadmin.exe
3737
- Path: C:\Windows\SysWOW64\bitsadmin.exe
38-
Code_Sample:
39-
- Code:
4038
Detection:
4139
- Sigma: https://github.com/SigmaHQ/sigma/blob/62d4fd26b05f4d81973e7c8e80d7c1a0c6a29d0e/rules/windows/process_creation/proc_creation_win_bitsadmin_download.yml
4240
- Sigma: https://github.com/SigmaHQ/sigma/blob/62d4fd26b05f4d81973e7c8e80d7c1a0c6a29d0e/rules/web/proxy_generic/proxy_ua_bitsadmin_susp_tld.yml

yml/OSBinaries/Certutil.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Commands:
1212
MitreID: T1105
1313
OperatingSystem: Windows vista, Windows 7, Windows 8, Windows 8.1, Windows 10, Windows 11
1414
- Command: certutil.exe -verifyctl -f {REMOTEURL:.exe} {PATH:.exe}
15-
Description: Download and save an executable to disk in the current folder when a file path is specified, or %LOCALAPPDATA%low\Microsoft\CryptnetUrlCache\Content\[hash] when not.
15+
Description: Download and save an executable to disk in the current folder when a file path is specified, or `%LOCALAPPDATA%low\Microsoft\CryptnetUrlCache\Content\<hash>` when not.
1616
Usecase: Download file from Internet
1717
Category: Download
1818
Privileges: User
@@ -26,7 +26,7 @@ Commands:
2626
MitreID: T1564.004
2727
OperatingSystem: Windows vista, Windows 7, Windows 8, Windows 8.1, Windows 10, Windows 11
2828
- Command: certutil.exe -URL {REMOTEURL:.exe}
29-
Description: Download and save an executable to %LOCALAPPDATA%low\Microsoft\CryptnetUrlCache\Content\[hash].
29+
Description: Download and save an executable to `%LOCALAPPDATA%low\Microsoft\CryptnetUrlCache\Content\<hash>`.
3030
Usecase: Download file from Internet
3131
Category: Download
3232
Privileges: User

yml/OSBinaries/Cmdkey.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,3 @@ Detection:
1919
Resources:
2020
- Link: https://web.archive.org/web/20230202122017/https://www.peew.pw/blog/2017/11/26/exploring-cmdkey-an-edge-case-for-privilege-escalation
2121
- Link: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/cmdkey
22-
Acknowledgement:
23-
- Person:
24-
Handle:

yml/OSBinaries/Eudcedit.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Author: Matan Bahar
55
Created: 2025-08-07
66
Commands:
77
- Command: eudcedit
8-
Description: Once executed, the Private Charecter Editor will be opened - click OK, then click File -> Font Links. In the next window choose the option "Link with Selected Fonts" and click on Save As, then in the opened enter the command you want to execute.
8+
Description: Once executed, the Private Charecter Editor will be opened - click OK, then click File -> Font Links. In the next window choose the option "Link with Selected Fonts" and click on Save As, then in the opened enter the command you want to execute.
99
Usecase: Execute a binary or script as a high-integrity process without a UAC prompt.
1010
Category: UAC Bypass
1111
Privileges: Administrator

yml/OSBinaries/Eventvwr.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Author: Jacob Gajek
55
Created: 2018-11-01
66
Commands:
77
- Command: eventvwr.exe
8-
Description: During startup, eventvwr.exe checks the registry value HKCU\Software\Classes\mscfile\shell\open\command for the location of mmc.exe, which is used to open the eventvwr.msc saved console file. If the location of another binary or script is added to this registry value, it will be executed as a high-integrity process without a UAC prompt being displayed to the user.
8+
Description: During startup, eventvwr.exe checks the registry value `HKCU\Software\Classes\mscfile\shell\open\command` for the location of mmc.exe, which is used to open the eventvwr.msc saved console file. If the location of another binary or script is added to this registry value, it will be executed as a high-integrity process without a UAC prompt being displayed to the user.
99
Usecase: Execute a binary or script as a high-integrity process without a UAC prompt.
1010
Category: UAC Bypass
1111
Privileges: User
@@ -15,7 +15,7 @@ Commands:
1515
- Application: GUI
1616
- Execute: EXE
1717
- Command: ysoserial.exe -o raw -f BinaryFormatter - g DataSet -c "{CMD}" > RecentViews & copy RecentViews %LOCALAPPDATA%\Microsoft\EventV~1\RecentViews & eventvwr.exe
18-
Description: During startup, eventvwr.exe uses .NET deserialization with %LOCALAPPDATA%\Microsoft\EventV~1\RecentViews file. This file can be created using https://github.com/pwntester/ysoserial.net
18+
Description: During startup, eventvwr.exe uses .NET deserialization with `%LOCALAPPDATA%\Microsoft\EventV~1\RecentViews` file. This file can be created using https://github.com/pwntester/ysoserial.net
1919
Usecase: Execute a command to bypass security restrictions that limit the use of command-line interpreters.
2020
Category: UAC Bypass
2121
Privileges: Administrator

0 commit comments

Comments
 (0)