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 requestesapuk or checks for and fetches kid specified in the JWT header and pull from the jkuSIGN 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.orgscantxt.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.appx-scanner-token: eyJ0eXAiOiJKV1QiLCJraWQiOiIxYTJiM2MiLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJzY2FudHh0LmFwcCIsImlhdCI6MTY2OTE2NTAyNywiYXVkIjoic2NhbnR4dC5vcmcifQ.I_b2AC0rwpjcz_7CM8Abr8aOn-HbKEWmU4HN9iayyMXyOk9bY-jXEji47mxMmFhRboGbCJYqlNJoHJNMbCeu-Qscantxt.org receives HTTP request header x-scanner with the value _scanner.scantxt.appscantxt.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