{"record":{"id":"48a323b5fe72df5f","repo":"projectdiscovery/nuclei","slug":"invalid-pl-option-data-range","errorCode":null,"errorMessage":"invalid PL option data range","messagePattern":"invalid PL option data range","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/js/libs/mssql/fingerprint.go","lineNumber":218,"sourceCode":"\n\tposition := 8\n\tvar tokens []optionToken\n\tfor position < len(response) {\n\t\tif response[position] == tdsTerminator {\n\t\t\tbreak\n\t\t}\n\t\tif position+5 > len(response) {\n\t\t\treturn out, fmt.Errorf(\"truncated PL option token\")\n\t\t}\n\t\ttoken := response[position]\n\t\toffset := binary.BigEndian.Uint16(response[position+1 : position+3])\n\t\tlength := binary.BigEndian.Uint16(response[position+3 : position+5])\n\t\tvar data []byte\n\t\tif length > 0 {\n\t\t\tstart := int(offset) + 8\n\t\t\tend := start + int(length)\n\t\t\tif start < 8 || end > len(response) {\n\t\t\t\treturn out, fmt.Errorf(\"invalid PL option data range\")\n\t\t\t}\n\t\t\tdata = response[start:end]\n\t\t}\n\t\ttokens = append(tokens, optionToken{token: token, offset: offset, length: length, data: data})\n\t\tposition += 5\n\t}\n\tif position >= len(response) || response[position] != tdsTerminator {\n\t\treturn out, fmt.Errorf(\"missing PL option terminator\")\n\t}\n\tif len(tokens) == 0 {\n\t\treturn out, fmt.Errorf(\"no PL option tokens\")\n\t}\n\tif tokens[0].token != plTokenVersion {\n\t\treturn out, fmt.Errorf(\"first PL option must be VERSION\")\n\t}\n\tif len(tokens[0].data) < 4 {\n\t\treturn out, fmt.Errorf(\"VERSION option too short\")\n\t}","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/projectdiscovery/nuclei/blob/265b3a3dec374741614e342f813c10f8b38d2bb7/pkg/js/libs/mssql/fingerprint.go#L200-L236","documentation":"Each pre-login option header points at its data via an offset (relative to the 8-byte TDS header) and a length; the window response[offset+8 : offset+8+length] must lie fully inside the packet. This error fires when that window starts before byte 8 or runs past the end of the response — the option table references data that does not exist. It is one of the errNotMssql-wrapped parse failures, so IsMssql turns it into (false, nil) while FingerprintMssql returns 'not a mssql service: invalid PL option data range'.","triggerScenarios":"mssql.FingerprintMssql / IsMssql against a replying service whose option offsets/lengths point outside the packet: corrupt banners, non-TDS protocols emitting 0x04 0x01 prefixes, or hand-built pre-login responses with wrong offset math.","commonSituations":"Service emulation (honeypots, tarpits) on 1433; unit tests with malformed fixture packets; off-by-one bugs in custom TDS implementations being scanned.","solutions":["Use mssql.IsMssql to classify the port first; false means the reply failed MSSQL validation","Capture the reply with tcpdump and manually decode the option table (5-byte entries after byte 8) checking each offset+8+length against the packet size","Cross-check with sqlcmd or a known-good TDS client before trusting the endpoint","Fall back to banner/nmap service detection for this target"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (!mssql.IsMssql(host, port)) { return; }","typeGuard":null,"tryCatchPattern":"try { const info = mssql.FingerprintMssql(host, port); }\ncatch (e) { if (String(e).includes('invalid PL option data range')) log('bad TDS option table from ' + host); else throw e; }","preventionTips":["Gate fingerprinting on a positive IsMssql result","Treat bounds-check failures as non-MSSQL classification, not transient errors","Avoid retrying parse failures — they are deterministic per reply bytes"],"tags":["mssql","tds","protocol-parsing","fingerprint","bounds-check","network"],"backgroundTag":null,"analyzedSha":"265b3a3dec374741614e342f813c10f8b38d2bb7","analyzedAt":"2026-08-15T20:05:51.855Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}