Scanners MUST identify themselves to servers. This is to ensure that servers can make decisions about allowing, rejecting or dropping activity and perform any verification of the scanning. It also allows server operators to know who to contact in the event of any queries or abuse.
For some protocols being scanned, such as HTTP, this can be achieved using request headers. Other protocols may not have a mechanism for direct identification, so in that case indirect identification is necessary using DNS PTR records.
For scanners primarily using the /.well-known/scanner.json
endpoint the Referer
should be used, for example:
Referer: https://www.scantxt.app/.well-known/scanner.json
For scanners primarily using DNS TXT records, the User-Agent
should be used, for example:
User-Agent: _scanner.scantxt.app
Additionally, scanners MAY also set the X-Scanner
header to either the .well-known
endpoint, _scanner
DNS record, or just the domain name.
Scanners MAY set all headers, for example:
Referer: https://www.scantxt.app/.well-known/scanner.json
User-Agent: _scanner.scantxt.app
X-Scanner: scantxt.app
The scanning IP should have a DNS PTR record pointing to the _scanner
subdomain. Additionally, that subdomain MAY set the A, AAAA or CNAME records such that the scanning IP can be resolved.
If, for example, the scanning IP was 192.168.0.1
:
dig +short -x 192.168.0.1
# _scanner.scantxt.app.
dig +short _scanner.scantxt.app
# 192.168.0.1
dig +short _scanner.scantxt.app TXT
# "v=SCANNER1; ..."
The following mechanism is how a scanned entity or target can verify the authenticity of a scan. It may even be possible for edge infrastructure to perform this verification before allowing the request to continue to an origin.
There are three mechanisms to enable verification.
SIGN
- Using asymmetric encryption to sign each request (recommended)HASH
- Generating a unique private hash per asset (simpler per asset)PRSH
- Using a pre-shared alphanumeric secret that the scanner sets per account or asset (simplest)Using SIGN
requires a scanner to have a ECDSA key pair, where the public key is available in either the puk
field or a JWKS endpoint (specified in the jku
- JWKS URI - field).
If using signing, the scanner MUST include sign
in the sgm
, set an attribute in the esa
- enabled signature attribute, and set either jku
or puk
. Both jku
and puk
MAY be used, and at least one MUST be set.
Using HASH
requires a scanner to use a HMAC utilising the asset and a private secret.
If using hashing, the scanner MUST include hash
in the sgm
and set an attribute in the esa
. jku
and puk
MAY be set if the HMAC function can utilise a public key for out-of-band verification.
Using PRSH
requires the scanner MUST include prsh
in the sgm
and set an attribute in the esa
. jku
and puk
MAY be set for use with the other mechanisms but will be ignored for pre-shared secrets. This method requires the user or system to interact with the scanner in order to verify the secret matches.
sgm
to sign
and configures a jku
and/or puk
{"typ": "JWT", "kid": "abc", "alg": "ES256"}
typ
is JWT
(scan
TBC)kid
is the key identifier to lookup from the JWKS endpoint{"iss": "scantxt.app", "iat": 123, "aud": "example.com"}
iss
is the scanneriat
is the issued at time, or scan time, which is the Unix time integer in secondsaud
is the targetBase64URL(header).Base64URL(payload)
Base64URL(header).Base64URL(payload).Base64URL(signature)
esa
field when performing the scanscanner
record and identifies the sgm
, esa
and jku
and/or puk
fieldsesa
in the request - if missing, drop the requestesa
puk
or checks for and fetches kid
specified in the JWT header and pull from the jku
SIGN
Examplescantxt.app
has an EC key pair:
Key ID: 1a2b3c
Private key:
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgt2w+b8mNigBWx8wi
5YkGg6JaIQfKNRlKDCH/+MZVlkahRANCAAQwDYA3okEJGV/52VuaOdSMhM42Xm3u
d2KtXzAT7OTDg/OeItUJWmxLbZqfxcX9qxKSwyzx+Te465pXg15V8h2x
-----END PRIVATE KEY-----
Public key:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMA2AN6JBCRlf+dlbmjnUjITONl5t
7ndirV8wE+zkw4PzniLVCVpsS22an8XF/asSksMs8fk3uOuaV4NeVfIdsQ==
-----END PUBLIC KEY-----
scantxt.app
sets the public key in the JWKS at https://scantxt.app/.well-known/scanner-jwks.json
and configures the _scanner.scantxt.app
record with the sgm
, jku
and esa
fields:
"v=SCANNER1; sgm=sign; jku=https://scantxt.app/.well-known/scanner-jwks.json; esa=http_header:x-scanner-token; info=https://www.scantxt.org; contacts=mailto:scantxt.app-scanner@olliejc.uk; type=banner_passive,crawler_passive,configuration_passive;"
scantxt.app
initiates a scan to scantxt.org
scantxt.app
creates a header and payload:
{"typ": "JWT", "kid": "1a2b3c", "alg": "ES256"}
{"iss": "scantxt.app", "iat": 1669165027, "aud": "scantxt.org"}
scantxt.app
signs eyJ0eXAiOiJKV1QiLCJraWQiOiIxYTJiM2MiLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJzY2FudHh0LmFwcCIsImlhdCI6MTY2OTE2NTAyNywiYXVkIjoic2NhbnR4dC5vcmcifQ
with its private key to create the JWTscantxt.app
sets HTTP request headers:
x-scanner: _scanner.scantxt.app
x-scanner-token: eyJ0eXAiOiJKV1QiLCJraWQiOiIxYTJiM2MiLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJzY2FudHh0LmFwcCIsImlhdCI6MTY2OTE2NTAyNywiYXVkIjoic2NhbnR4dC5vcmcifQ.I_b2AC0rwpjcz_7CM8Abr8aOn-HbKEWmU4HN9iayyMXyOk9bY-jXEji47mxMmFhRboGbCJYqlNJoHJNMbCeu-Q
scantxt.org
receives HTTP request header x-scanner
with the value _scanner.scantxt.app
scantxt.org
looks up the scanner
record from scantxt.app
and finds the sgm
, jku
and esa
fieldsscantxt.org
checks for the esa
HTTP request header (x-scanner-token: eyJ0eXAiOiJKV1QiLCJraWQiOiIxYTJiM2MiLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJzY2FudHh0LmFwcCIsImlhdCI6MTY2OTE2NTAyNywiYXVkIjoic2NhbnR4dC5vcmcifQ.I_b2AC0rwpjcz_7CM8Abr8aOn-HbKEWmU4HN9iayyMXyOk9bY-jXEji47mxMmFhRboGbCJYqlNJoHJNMbCeu-Q
)scantxt.org
needs to fetch the public key, as it’s not in the puk
, so decodes the JWT to get the kid
value (1a2b3c
)scantxt.org
verifies and decodes the JWT using the key found in the jku
endpoint (https://scantxt.app/.well-known/scanner-jwks.json
)scantxt.org
asserts that the request comes from scantxt.app