forked from mirrors/kingfisher
Merge pull request #14 from mongodb/development
This PR (v1.16.0) improves HTML detection, removes the cargo-nextest installation during test running, and adds new secret scanning rules for various services (including 1Password and DroneCI). Updated the HTML detection logic in the HTTP validation code Added new secret rules for WireGuard, Twitter, Slack, 1Password, DroneCI, and others Removed the cargo-nextest installation step from the Makefile
This commit is contained in:
commit
b172a2ff89
14 changed files with 360 additions and 68 deletions
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [1.16.0]
|
||||
- Fix: HTML detection now requires both HTML content-type and "<html" tag, fixing webhook false negatives
|
||||
- Removed cargo-nextest installation during test running
|
||||
- Added rules for 1password, droneci
|
||||
|
||||
## [1.15.0]
|
||||
- Ensuring temp files are cleaned up
|
||||
- Applying visual style to the update check output
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ publish = false
|
|||
|
||||
[package]
|
||||
name = "kingfisher"
|
||||
version = "1.15.0"
|
||||
version = "1.16.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
|
|
|||
7
Makefile
7
Makefile
|
|
@ -400,13 +400,6 @@ check-rust:
|
|||
fi
|
||||
|
||||
tests:
|
||||
@echo "🔍 checking for cargo-nextest …"
|
||||
@if command -v cargo-nextest >/dev/null 2>&1; then \
|
||||
echo "✅ cargo-nextest already present"; \
|
||||
else \
|
||||
echo "📦 installing cargo-nextest …"; \
|
||||
cargo install --locked cargo-nextest || true; \
|
||||
fi
|
||||
@echo "▶ running tests …"; \
|
||||
if command -v cargo-nextest >/dev/null 2>&1; then \
|
||||
cargo nextest run --workspace --all-targets; \
|
||||
|
|
|
|||
42
data/rules/droneci.yml
Normal file
42
data/rules/droneci.yml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
rules:
|
||||
- name: DroneCI Access Token
|
||||
id: kingfisher.drone.1
|
||||
pattern: |
|
||||
(?xi)
|
||||
\b
|
||||
(?:drone|droneci|drone[_-])
|
||||
(?:.|[\n\r]){0,16}?
|
||||
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
|
||||
(?:.|[\n\r]){0,16}?
|
||||
\b
|
||||
(
|
||||
ey[A-Za-z0-9_-]{30,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}
|
||||
|
|
||||
[a-f0-9]{32,64}
|
||||
)
|
||||
\b
|
||||
min_entropy: 3.5
|
||||
confidence: medium
|
||||
examples:
|
||||
- export DRONE_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiZGVtbyJ9.GEPa7kCDdw4nruBKgLkQF1EGMZVvJ1kM4sMp9p8a1x4
|
||||
- drone_token = fe8c402a51e6629aa1f43a4234afee81
|
||||
validation:
|
||||
type: Http
|
||||
content:
|
||||
request:
|
||||
method: GET
|
||||
url: https://cloud.drone.io/api/user
|
||||
headers:
|
||||
Authorization: "Bearer {{ TOKEN }}"
|
||||
Accept: application/json
|
||||
response_matcher:
|
||||
- report_response: true
|
||||
- type: StatusMatch
|
||||
status: [200]
|
||||
- type: WordMatch
|
||||
words: ['"email"']
|
||||
match_all_words: true
|
||||
references:
|
||||
- https://docs.drone.io/api/overview/
|
||||
- https://0-8-0.docs.drone.io/api-authentication/
|
||||
- https://docs.drone.io/server/user/machine/
|
||||
|
|
@ -3,7 +3,7 @@ rules:
|
|||
id: kingfisher.intercom.1
|
||||
pattern: |
|
||||
(?xi)
|
||||
(?:intercom(?:_access)?|ic)
|
||||
(?:intercom|ic)
|
||||
(?:.|[\n\r]){0,16}?
|
||||
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
|
||||
(?:.|[\n\r]){0,16}?
|
||||
|
|
|
|||
|
|
@ -28,43 +28,6 @@ rules:
|
|||
adoConn.Open("Provider=SQLOLEDB.1;User ID=specialbill_user; " & "Password =specialbill_user;Initial Catalog=SpecialBill_PROD;Data Source=uszdba01;")
|
||||
- |
|
||||
"driver={SQL Server};server=(#{datastore['DBHOST']});database=#{datastore['DBNAME']};uid=#{datastore['DBUID']};pwd=#{datastore['DBPASSWORD']}"
|
||||
negative_examples:
|
||||
- 'def login(self, user = "", password = "", domain = ""):'
|
||||
- |
|
||||
if datastore['VERBOSE']
|
||||
text = ''
|
||||
text << "User=#{username}, "
|
||||
text << "Password=#{password}, "
|
||||
text << "Domain=#{domain}, "
|
||||
text << "Full Name=#{full_name}, "
|
||||
text << "E-mail=#{e_mail}"
|
||||
print_good(text)
|
||||
- |
|
||||
if (len < ulen + wlen + 2)
|
||||
break;
|
||||
user = (char *) (p + 1);
|
||||
pwd = (char *) (p + ulen + 2);
|
||||
p += ulen + wlen + 2;
|
||||
- |
|
||||
/* Set default values */
|
||||
server = xmalloc(sizeof(*server));
|
||||
server->user = "anonymous";
|
||||
server->password = "busybox@";
|
||||
- |
|
||||
System.out.println("Here we go...");
|
||||
String url = "jdbc:msf:sql://127.0.0.1:8080/sample";
|
||||
String userid = "userid";
|
||||
String password = "password";
|
||||
- |
|
||||
char *domain = NULL;
|
||||
char *user = NULL;
|
||||
char *password = NULL;
|
||||
- |
|
||||
<?php
|
||||
\$user = \$_POST["username"];
|
||||
\$pwd = \$_POST["password"];
|
||||
\$otherdata = \$_POST["otherdata"];
|
||||
?>
|
||||
references:
|
||||
- https://docs.aws.amazon.com/redshift/latest/mgmt/configure-odbc-connection.html
|
||||
- https://docs.microsoft.com/en-us/azure/data-explorer/kusto/api/connection-strings/kusto
|
||||
|
|
|
|||
53
data/rules/onepassword.yml
Normal file
53
data/rules/onepassword.yml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
rules:
|
||||
- name: 1Password Service-Account Token
|
||||
id: kingfisher.1password.2
|
||||
pattern: |
|
||||
(?xi)
|
||||
\b
|
||||
(
|
||||
ops_eyj[A-Za-z0-9_-]{80,500}
|
||||
)\b
|
||||
min_entropy: 4.0
|
||||
confidence: medium
|
||||
examples:
|
||||
- export OP_SERVICE_ACCOUNT_TOKEN=ops_eyJzaWduSW5BZGRyZXNzIjoibXkuMXBhc3N3b3JkLmV1IiwidXNlckF1dGgiOnsibWV0aG9kIjoiU1JQZy00MDk2IiwiYWxnIjoiUEJFUzJnLUhTMjU2IiwiaXRlcmF0aW9ucyI6NjUwMDAwLCJzYWx0IjoiUUNYYy1wTDUtakdCaDlTVjFHb1lpUSJ9LCJlbWFpbCI6ImF2ZGxyZ3JramU3dm9AMXBhc3N3b3Jkc2VydmljZWFjY291bnRzLmV1Iiwic3JwWCI6IjExNjFkMmYwNTQ3NDgxNTBmOTEwOWMxZDEzYTllZjFiNGY0ZjZiYzhlNTFlNWZkMWI5NmI5ZjQwZjY3NWEyNTciLCJtdWsiOnsiYWxnIjoiQTI1Nkd1111111111
|
||||
validation:
|
||||
type: Http
|
||||
content:
|
||||
request:
|
||||
method: GET
|
||||
url: https://events.1password.com/api/v2/auth/introspect
|
||||
headers:
|
||||
Authorization: "Bearer {{ TOKEN }}"
|
||||
Accept: application/json
|
||||
response_matcher:
|
||||
- report_response: true
|
||||
- type: StatusMatch
|
||||
status: [200]
|
||||
- type: WordMatch
|
||||
words: ['"features"']
|
||||
match_all_words: true
|
||||
references:
|
||||
- https://developer.1password.com/docs/service-accounts/security
|
||||
- https://developer.1password.com/docs/service-accounts/get-started
|
||||
- https://developer.1password.com/docs/cli/environment-variables
|
||||
- https://developer.1password.com/docs/events-api/reference
|
||||
|
||||
- name: 1Password Account Secret Key
|
||||
id: kingfisher.1password.2
|
||||
pattern: |
|
||||
(?xi)
|
||||
\b
|
||||
(
|
||||
A[0-9]-[A-Z0-9]{6}-[A-Z0-9]{6}-[A-Z0-9]{5}(?:-[A-Z0-9]{5}){3}
|
||||
)
|
||||
\b
|
||||
min_entropy: 3.8
|
||||
confidence: medium
|
||||
prevalidated: true
|
||||
examples:
|
||||
- A3-R69SQK-TZ9KPW-8MXYD-6W373-V7GHJ-EDJQW
|
||||
- A3-ASWWYB-798JRY-LJVD4-23DC2-86TVM-H43EB
|
||||
references:
|
||||
- https://support.1password.com/secret-key-security/
|
||||
- https://developer.1password.com/files/1password-white-paper.pdf
|
||||
|
|
@ -10,7 +10,7 @@ rules:
|
|||
-----END\ .{0,20}\ ?PRIVATE\ KEY\ ?.{0,20}-----
|
||||
min_entropy: 4.5
|
||||
confidence: high
|
||||
prevalidated: false
|
||||
prevalidated: true
|
||||
examples:
|
||||
- |
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
|
|
@ -62,7 +62,7 @@ rules:
|
|||
(?: [^a-zA-Z0-9+/=] | $ )
|
||||
min_entropy: 4.5
|
||||
confidence: high
|
||||
prevalidated: false
|
||||
prevalidated: true
|
||||
examples:
|
||||
- 'PRIVATE_KEY_B64=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBb3kxWFh1VkFRcHFIYlFFMDVta2hyTmcvMTI0Ri8ySzlPYW5pelpUWlVVaEswOFU4CkxhaC9SbVVsWHFRMDEvU255aktGOWZqUDhFcU1OZ1dpamUzYmVwL3RPOVpTMEFUMi9PVlJXeS9TOG52RDQ5WTMKenMxMktSbERhR2lZc0RsYUZrbHJkeDQ4RWhRVmdHN3hmWE1jaC9OejJzc2FEby9kRkNBOW80TkZZQWUzM2UveApWNVo1UHNkWkl6dkNZQVlCNDRoUEtpN3JXRE1IbFdzM1kvVkVtQXMzSzVNK2QvL3QzRHB4WnBEbWJERGdYa2w2CjZUdDh3VXloUVZ3MkZpMStobTF1T2QwYjFkaW9aNko2OXNTT2JOZXpSR3YxYjdZaFltT0JKL1JBbHN5ZHoxTmgKVXpXT1lYV0Z1OGJrOU9JM3lQMEc0TE84QjhtbWRldE1RVVoyelFJREFRQUJBb0lCQUN2ckhUUHVVZ0JiSlE0QwpvQ0ZQdEgrWDZIN3NIdk1ndVR0VzdUTlYxN1BYMkVQdE53ZzI3S0tld0pNYmNSbWF3THBjSk5BU09xMDY4MGZxCjlsaHE1NEsybnB4WFVBeXErV3NSc1hid2hUODhibm5aQTBaRzZJR2hTaEpFN0t1cGxBU2htQ29FV2ppbmJTNFgKTGlvTW5HWSs4VFMzSzNrMTRWUDBaWUtuNXprMERHZnFBMEo0VTRXSmxUeGwrTWZxd0pJOTlrcTdHbFVlZkdncQpuK3Q1d2NrV3BPbTd5TUJjZTlTSXlmTm54bnU3TkZYQm50VTN5RGxSUThWUWZmNEtRMzJCaWNiYlJWemR1TThNCnNxMU5CZWNzL0EzUXRvdG1nWUc4d094ZXpNS3Iyays2QzB2NmlFc0h5T0lmR25GWktSZDJFd0dnWlo3aytURHUKUUYrcjd1VUNnWUVBMkRqNUJoYmpybDFRNTZya3BhTGFvVldRV1Y5YUYzUUJtNlNZM2VQYmlvY2JNR2k1ak1ESQpkSjdJVXlLYUljK3BNV1RQYlBmVUd2WmNENlczZDFBNUNUSnFuWHVuVlY3czRqaWJ6WDZUbjhNM3IrMHZTZnNZCmdPMHBtRFpndlNqaVZTRUNBQTZFOFUxQ1lFZU5KUDFDOW12cGJVNzJRTEpndWp3M3JMb2oyYmNDZ1lFQXdUSXYKOUNSeWNOQXRBbDcvUHdWZGh5eXRvVHBSRnZDSU1HSVk5SjMxZ3lva0ZlaFQvWjQ4WkF6anl6ZTBSUXYzdGUxTQoveVJMQkVETGkwbEtrZFVXckVkaVR3dm1KdkpwMDZ0OEdCbERsK25ycXVLWTFxVThDbTR5cis4QzZtRThkVnZrClNINXBhRXptOERFTE1wSjhGVTZFYnhmZHZjRzZmSGx6dnVnZmc1c0NnWUFFQ1BRa3QvS2h3MTRLSkxkRm5BZG0KY1ZsVFFhTkZ3c1Z3NlI1dExaNWdOR3MrZVFYVmFaZVVEWTZCZHFqWHJxOWltNVgvVzVTYXVEUTVtb2NVOCt0TQpqNk5Mc3c0SldzOGkzWm1TdVNUNkcwT0R4ZkpXK0JlWitGTUpZeUpsQlVsTCsyUzFLWkF6akpTTGhXcE40V2dKCmZ6UUk5U3RGUTg3b1NzMWpMTW9VZXdLQmdGOE9CMlFURHErTTdhaE4vejROc0wvU2JyZDJEdkcvZFBLQlFaQVIKcS90V0g1MGJ5ejlzdkgvcGk2YXdDS1UwUnpPZXh4UjkwZDhNMWxqNHZaVFZDQ3ZKajRnZTdhVlovbEdqL1JHSwpWS1NJOW1nRXgzaE1vaWJybzByR3lXTnlaaUhFRGFUUmRhRll2UU9PemRpYkZDd1RqcnR1UGE2Z2c5VzhtQU5sCkNDUmpBb0dBSTRIbnpyV3kzaU5kR2xqVnh4bW1DN1V0c0MvajJBUEZpcHc0ZHJ0U2NsMDFRZzF5WkowbDNBTk4KOU5lTmVSUUFzN3pFTng2T1B1SzlxYy83T1ROMTJKaHdoUTIzdXZwNjZjV0krdTRjcVpOZTJyZVFVVWVmM3psbQpMcXRmOU50VHp5M3pjMGZQcGoxQnBlRmxHSG9SVDhjVHpBWjFTeGwyZWChazlqS2RVeDQ9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t'
|
||||
- ' "privateKey": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBbUhKOEJHdTFYZUZ4aENVQXBrNHNSTVI4RnRTdGtyMEx0OWtWTGNSUjRFWitiOWhHCmR0blJpOFhqV3d5MU5zMHliMkJMdHBpVHZKSFVKTUphWXluZ2ZkZnZhcWhocm1yYm5vV0pLQkxmeUxwTXFNS1EKQ3RialFxbnVrQURJUWVQd2ZGeTNpVHkxd1JkRC9zTUs1U0VtV0Fxb0pZQk50eTFZZzA2UzVkYVlPM2xjY3hrYQpQWjRjcm9McWF6Ny9tU3dDVTR5VWRSb3h4WVF4VG1MZXg5M2tqU09TTmdpK0FXc0lCbjV3UHI0VHNuVHFSeWpIClN2aEdMdk9YREpRYWZRdk56WjFSL1FYMzlOQk9xOEVKZW5pWXdaUm9uNVcvNVhMYW94MFFyUGhrY1BES3A5SVUKeHpJakUwWlNmMStUK1FFbTQ3TkFtSnhvZjFhdGRFVzZDTCtheHdJREFRQUJBb0lCQUQ3enI4REhsWnFSK1NWZgpmbGd1bWRzLzVCb3Rjd3ZRWXlGbFZIaVV4RmEvNVlCY0tDVDJKN0QzWTc1NmplNTJaK2hVTkkvUGk5cG53ZG40CkpBa2xCdDRRcUg0NzBES05UK216TFFOT1gvanM3YkVXdnhLcTBDZjhNbFptN0V0QlRGS2VtdS9pRVJBT2duYVcKcGs0ZUZVNXdBQ1dVU1FObWgxR1p4ZEdCZjFXM1VjUnQxcFRvOEtQTDluZm4vSGJiRFNsQkNVL3VIcWd2TSt2cApmTE03bzRIVDZ1K1ZzU00rWGZqeDhpeE5ZRHdoalNuKzQyZm13d1d3ZzJISHUrdUozZ1pUSWQwRUI1VW9hdUNjCjZUTlVtcEJscjU5UGFmVkZRWUY1S3VxaHJXKzVQaWpHcHBZcXg4Ynl6aFpOQzkwZnl5V0NXcXg2eGFZVm5OdzgKNkJmUXM2a0NnWUVBeVlyRVg1NU1RTzJnWDY2TGwxaGJDMzNzWk1OZzloVG1SK1doSTFjNksvbFZ1TFoyL0RPdwpsYTZ6eHdBU204Z0ZyVUFYbUljV2h2b3FwWGVzNWZzOVZKeDlNT0ZVYVBrckRPQllnY1laMUR6VVNVOHc3SSttCnlyV3hRUkRNajhvSGpRbHVpM0s2MzZucm5RajhxOGkvQ2dranVPcHJGZnliMzVEMFlDdjVXZzBDZ1lFQXdhT3cKRWFhN0l1MjFGa08vbmFjdVhjSnBhNkVlUTNqZFNlNlRQaXZ6bVVXU0haeGJuUy9XSnJaRjQwSExzUWxOZHl0ZgpNTTBKZFU0VmMyR0NVc1pMYjdQSmJwdVRqRERSSHJXV1pCMnhiemF0K3A3N2RzNWlOcXFRcTZ6M0syUVh4Y3ZTCis5am5VZXpDU2Y0N1R1OWNTTW96V3hTMW82b1BPSFdHVFRvdHR5TUNnWUFQdWc1Y3o4TnZoWnR3Ry9TMG1LWnkKSFI5bk5YL0pkQlFNSkRVUXh1dTVKcm16c2psU3NNM2t3RDh6RmlSZGw1d3B5c2lNbEc0RGxsM2hqNWNrVXhpVQpFNm9KT0d3WHpPbTVGWUNTajl6UUhQY0x5V3d0NlgvQWJiRXBQS0JaMEJBS3gyT2k2ZzcvQ1FsanRhSFIzZFphCmVDQWJlOTlqVmRUcit5bTJuM2ZUdVFLQmdBMm5TZ25rbEx0Z3dXMEJkK2hZMm1jWUJ6RGttbXF0Z2dUdGdvcFcKdFFWd3AxM1pJWWlTeituSTNtS295QUVDbytpc01Ua1NyQUVPY1dyQ1RGc2p5anZsRkdYdEtGa3hNLzJUVmpoVwo4NlRnMlNHYnhpVlpaZ2x1dTJhdmVub2Z3NkZadnRXdE5KcE5OR0hkUURkUG4xVXVsTEp1WW1SWTRGdmR4WXQ2CmQ3QzdBb0dBRUsvalFiZ0l3OXFLQUNOZ0JySnB1cU5Ham9JajFoQTRlb29DMXp1bFEyZUpnZ2J5OTBpSDg2VzEKM0xyOVZMVFkyc2JKTzlqekZVR0lOL01BOEhYQTE1a2grZHRibkRsdFRFZGNnenBCRzhCQUZRQ3hQWnBGWHhtZgpDUmhXN1l6RW1IeWJ4R0toR3NOK2M3NUhKTHZFSWwrRTh6eitXRk9xT240dkJXU1ZwSnc9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==",'
|
||||
|
|
@ -24,7 +24,7 @@ rules:
|
|||
-----
|
||||
min_entropy: 4.5
|
||||
confidence: high
|
||||
prevalidated: false
|
||||
prevalidated: true
|
||||
examples:
|
||||
- |-
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
|
|
@ -77,7 +77,7 @@ rules:
|
|||
)
|
||||
min_entropy: 4.5
|
||||
confidence: high
|
||||
prevalidated: false
|
||||
prevalidated: true
|
||||
examples:
|
||||
- |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
|
|
|
|||
|
|
@ -85,9 +85,9 @@ rules:
|
|||
\b
|
||||
(
|
||||
https://hooks\.slack\.com/services/
|
||||
T[a-z0-9_-]{8,12}/ # Team ID
|
||||
B[a-z0-9_-]{8,12}/ # Bot ID
|
||||
[a-z0-9_-]{20,30} # Webhook token
|
||||
T[a-z0-9_-]{8,12}/
|
||||
B[a-z0-9_-]{8,12}/
|
||||
[a-z0-9_-]{20,30}
|
||||
)
|
||||
\b
|
||||
min_entropy: 3.3
|
||||
|
|
@ -105,10 +105,9 @@ rules:
|
|||
- report_response: true
|
||||
type: WordMatch
|
||||
words:
|
||||
- ok
|
||||
- invalid_payload
|
||||
- type: WordMatch
|
||||
words:
|
||||
words:
|
||||
- "invalid_token"
|
||||
negative: true
|
||||
url: '{{ TOKEN }}'
|
||||
url: "{{ TOKEN }}"
|
||||
|
|
|
|||
38
data/rules/twitter.yml
Normal file
38
data/rules/twitter.yml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
rules:
|
||||
- name: X / Twitter Bearer Token (App-only)
|
||||
id: kingfisher.twitter.bearer.1
|
||||
pattern: |
|
||||
(?xi)
|
||||
\b
|
||||
(?:twitter|x.com|twtr)?
|
||||
(?:.|[\n\r]){0,16}?
|
||||
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN|BEARER)
|
||||
(?:.|[\n\r]){0,16}?
|
||||
\b
|
||||
(
|
||||
A{10,}[A-Za-z0-9_\-]{40,200}
|
||||
)
|
||||
\b
|
||||
min_entropy: 4.0
|
||||
confidence: medium
|
||||
examples:
|
||||
- "Twitter Secret: Bearer AAAAAAAAAAAAAAAAAAAb342O1ksbnZiwemJv0g9LrF6cRazflLJEDbmmuAr8GQbDkDrYHoqPM1xr8tmP5LGW1Vq_soJIsolxqOXtAPA2hUGXIBYubxWPLVZkUs7jYBizqlDRDSHRGXKP2nuCzdbNlxKcgMQzrm"
|
||||
- TWITTER_BEARER="AAAAAAAAAAAAAAxrrTSuijZNLygaTAEMAc_iY1nOdFRc1OP4fpMZiEkTQ4D-QaCvOhvBvpu5zwH67VvwE9MlmL78ptDdFP_FX6fgcJnQOcvsedOXNP9t3D1fYkJcJT3kXldAIJJ"
|
||||
validation:
|
||||
type: Http
|
||||
content:
|
||||
request:
|
||||
method: GET
|
||||
url: https://api.x.com/1.1/application/rate_limit_status.json
|
||||
headers:
|
||||
Authorization: "Bearer {{ TOKEN }}"
|
||||
Accept: application/json
|
||||
response_matcher:
|
||||
- report_response: true
|
||||
- type: StatusMatch
|
||||
status: [200]
|
||||
- type: WordMatch
|
||||
words: ['"rate_limit_context"']
|
||||
match_all_words: true
|
||||
references:
|
||||
- https://developer.x.com/en/docs/x-api/v1/developer-utilities/rate-limit-status/api-reference/get-application-rate_limit_status
|
||||
35
data/rules/wireguard.yml
Normal file
35
data/rules/wireguard.yml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
rules:
|
||||
- name: WireGuard Private Key
|
||||
id: kingfisher.wireguard.1
|
||||
pattern: PrivateKey\s*=\s*([A-Za-z0-9+/]{43}=)
|
||||
min_entropy: 3.3
|
||||
confidence: medium
|
||||
examples:
|
||||
- |
|
||||
[Interface]
|
||||
Address = 10.200.200.3/32
|
||||
PrivateKey = AsaFot43bfs1fEWjvtty+rGcjh3rP1H6sug1l3u19ix=
|
||||
DNS = 8.8.8.8
|
||||
references:
|
||||
- https://www.wireguard.com/quickstart/
|
||||
- https://manpages.debian.org/testing/wireguard-tools/wg.8.en.html
|
||||
- https://gist.github.com/lanceliao/5d2977f417f34dda0e3d63ac7e217fd6
|
||||
categories: [fuzzy, secret]
|
||||
|
||||
- name: WireGuard Preshared Key
|
||||
id: kingfisher.wireguard.2
|
||||
pattern: PresharedKey\s*=\s*([A-Za-z0-9+/]{43}=)
|
||||
min_entropy: 3.3
|
||||
confidence: medium
|
||||
examples:
|
||||
- |
|
||||
[Peer]
|
||||
PublicKey = [Server's public key]
|
||||
PresharedKey = uRsfsZ2Ts1rach4Zv3hhwcx6wa5fuIo2u3w7sa+7j81=
|
||||
AllowedIPs = 0.0.0.0/0, ::/0
|
||||
Endpoint = [Server Addr:Server Port]
|
||||
references:
|
||||
- https://www.wireguard.com/quickstart/
|
||||
- https://manpages.debian.org/testing/wireguard-tools/wg.8.en.html
|
||||
- https://gist.github.com/lanceliao/5d2977f417f34dda0e3d63ac7e217fd6
|
||||
categories: [fuzzy, secret]
|
||||
|
|
@ -989,4 +989,132 @@ rules:
|
|||
println!("Body: {:?}", owned_blob_match.validation_response_body);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// // ────────────────────────────────────────────────────────────────
|
||||
// // Slack Webhook – end-to-end validation test
|
||||
// // ────────────────────────────────────────────────────────────────
|
||||
// #[tokio::test]
|
||||
// async fn test_actual_slack_webhook_validation() -> anyhow::Result<()> {
|
||||
// use std::sync::Arc;
|
||||
|
||||
// use crossbeam_skiplist::SkipMap;
|
||||
// use http::StatusCode;
|
||||
// use rustc_hash::FxHashMap;
|
||||
|
||||
// use crate::{
|
||||
// blob::BlobId,
|
||||
// liquid_filters::register_all,
|
||||
// location::OffsetSpan,
|
||||
// matcher::{OwnedBlobMatch, SerializableCapture, SerializableCaptures},
|
||||
// rules::{
|
||||
// rule::{Confidence, Rule},
|
||||
// Rules,
|
||||
// },
|
||||
// validation::{validate_single_match, Cache},
|
||||
// };
|
||||
|
||||
// // 1️⃣ YAML snippet with the **exact** Slack rule
|
||||
// let slack_yaml = r#"
|
||||
// rules:
|
||||
// - name: Slack Webhook
|
||||
// id: kingfisher.slack.4
|
||||
// pattern: |
|
||||
// (?xi)
|
||||
// \b
|
||||
// (
|
||||
// https://hooks\.slack\.com/services/
|
||||
// T[a-z0-9_-]{8,12}/
|
||||
// B[a-z0-9_-]{8,12}/
|
||||
// [a-z0-9_-]{20,30}
|
||||
// )
|
||||
// \b
|
||||
// min_entropy: 3.3
|
||||
// confidence: medium
|
||||
// examples:
|
||||
// - https://hooks.slack.com/services/TY40v9sZ9/BxIqhIXIi/NGUyXK6nK7HMAqd0ASzXluoV
|
||||
// - https://hooks.slack.com/services/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
|
||||
// validation:
|
||||
// type: Http
|
||||
// content:
|
||||
// request:
|
||||
// headers:
|
||||
// Content-Type: application/json
|
||||
// method: POST
|
||||
// response_matcher:
|
||||
// - report_response: true
|
||||
// - type: WordMatch
|
||||
// words:
|
||||
// - invalid_payload
|
||||
// - type: WordMatch
|
||||
// words:
|
||||
// - "invalid_token"
|
||||
// negative: true
|
||||
// url: "{{ TOKEN }}"
|
||||
// "#;
|
||||
|
||||
// // 2️⃣ Load that YAML into a Rules object
|
||||
// let data = vec![(std::path::Path::new("slack_test.yaml"), slack_yaml.as_bytes())];
|
||||
// let rules = Rules::from_paths_and_contents(data, Confidence::Low)?;
|
||||
|
||||
// // 3️⃣ Pull the rule syntax & wrap into a Rule
|
||||
// let slack_rule_syntax = rules
|
||||
// .rules
|
||||
// .iter()
|
||||
// .find(|r| r.id == "kingfisher.slack.4")
|
||||
// .expect("Slack rule not found")
|
||||
// .clone();
|
||||
// let slack_rule = Rule::new(slack_rule_syntax);
|
||||
|
||||
// // 4️⃣ Provide a real-looking webhook URL (use one of the examples)
|
||||
// let token = "ENTER YOUR SLACK WEBHOOK URL HERE";
|
||||
|
||||
// // 5️⃣ Build OwnedBlobMatch stub
|
||||
// let blob_id = BlobId::new(&token.as_bytes());
|
||||
// let mut owned_blob_match = OwnedBlobMatch {
|
||||
// rule: slack_rule.into(),
|
||||
// blob_id,
|
||||
// finding_fingerprint: 0,
|
||||
// matching_input_offset_span: OffsetSpan { start: 0, end: token.len() },
|
||||
// captures: SerializableCaptures {
|
||||
// captures: vec![SerializableCapture {
|
||||
// name: Some("TOKEN".to_string()),
|
||||
// match_number: -1,
|
||||
// start: 0,
|
||||
// end: token.len(),
|
||||
// value: token.into(),
|
||||
// }],
|
||||
// },
|
||||
// validation_response_body: String::new(),
|
||||
// validation_response_status: StatusCode::OK,
|
||||
// validation_success: false,
|
||||
// calculated_entropy: 5.0,
|
||||
// };
|
||||
|
||||
// // 6️⃣ Prepare helpers and run validation
|
||||
// let parser = register_all(liquid::ParserBuilder::with_stdlib()).build()?;
|
||||
// let client = reqwest::Client::new();
|
||||
// let cache: Cache = Arc::new(SkipMap::new());
|
||||
// let dependent_vars = FxHashMap::default();
|
||||
// let missing_deps = FxHashMap::default();
|
||||
|
||||
// validate_single_match(
|
||||
// &mut owned_blob_match,
|
||||
// &parser,
|
||||
// &client,
|
||||
// &dependent_vars,
|
||||
// &missing_deps,
|
||||
// &cache,
|
||||
// )
|
||||
// .await;
|
||||
|
||||
// // 7️⃣ Inspect outcome (true ⇒ credential considered ACTIVE)
|
||||
// assert!(
|
||||
// owned_blob_match.validation_success,
|
||||
// "Slack webhook should be reported ACTIVE; body was {:?}",
|
||||
// owned_blob_match.validation_response_body
|
||||
// );
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -270,23 +270,26 @@ pub async fn retry_request(
|
|||
|
||||
/// Return `true` when the body is very likely HTML.
|
||||
///
|
||||
/// Heuristics (fast):
|
||||
/// 1. Content-Type header says “text/html” or “application/xhtml+xml”.
|
||||
/// 2. First 1 kB starts with “<” **and** contains “<html”.
|
||||
fn body_looks_like_html(body: &str, headers: &HeaderMap) -> bool {
|
||||
// ---- 1. header heuristic ---------------------------------------------
|
||||
if let Some(ct) = headers.get("content-type").and_then(|v| v.to_str().ok()) {
|
||||
let ct = ct.to_ascii_lowercase();
|
||||
if ct.contains("text/html") || ct.contains("application/xhtml") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
let header_says_html = headers
|
||||
.get("content-type")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.map(|ct| {
|
||||
let ct = ct.to_ascii_lowercase();
|
||||
ct.contains("text/html") || ct.contains("application/xhtml")
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
// ---- 2. early-body scan (<=1024 bytes) --------------------------------
|
||||
let probe = body[..body.len().min(1024)].to_ascii_lowercase();
|
||||
probe.starts_with('<') && probe.contains("<html")
|
||||
let body_looks_htmlish = probe.starts_with('<') && probe.contains("<html");
|
||||
|
||||
// ⇒ Only HTML if **both** header and body agree
|
||||
header_says_html && body_looks_htmlish
|
||||
}
|
||||
|
||||
|
||||
/// Validate the response by checking word and status matchers.
|
||||
pub fn validate_response(
|
||||
matchers: &[ResponseMatcher],
|
||||
|
|
@ -295,8 +298,7 @@ pub fn validate_response(
|
|||
headers: &HeaderMap,
|
||||
html_allowed: bool,
|
||||
) -> bool {
|
||||
// Since match_all_types is always true here, we simply require all word and status conditions
|
||||
// to hold.
|
||||
// Since match_all_types is always true here, we simply require all word and status conditions to hold.
|
||||
let word_ok = matchers
|
||||
.iter()
|
||||
.filter_map(|m| {
|
||||
|
|
@ -474,4 +476,38 @@ mod tests {
|
|||
// --- assert -----------------------------------------------------------
|
||||
assert!(result);
|
||||
}
|
||||
#[test]
|
||||
fn test_validate_response_slack_webhook() {
|
||||
// 1️⃣ Build matchers equivalent to rule kingfisher.slack.4
|
||||
let matchers = vec![
|
||||
ResponseMatcher::WordMatch {
|
||||
r#type: "word-match".to_string(),
|
||||
words: vec!["invalid_payload".to_string()],
|
||||
match_all_words: false, // rule omits this → default is false
|
||||
negative: false,
|
||||
},
|
||||
ResponseMatcher::WordMatch {
|
||||
r#type: "word-match".to_string(),
|
||||
words: vec!["invalid_token".to_string()],
|
||||
match_all_words: false,
|
||||
negative: true, // body must *not* contain “invalid_token”
|
||||
},
|
||||
];
|
||||
|
||||
// 2️⃣ Simulate the real Slack response you posted
|
||||
let body = "invalid_payload";
|
||||
let status = StatusCode::BAD_REQUEST; // 400
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("text/plain"),
|
||||
);
|
||||
|
||||
// 3️⃣ Call validate_response with html_allowed = false
|
||||
let ok = validate_response(&matchers, body, &status, &headers, false);
|
||||
|
||||
// 4️⃣ It *should* be valid (true) because all matcher conditions hold
|
||||
assert!(ok, "Slack webhook response should be considered ACTIVE");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue