zgrab2/modules/ipp/ipp.go
Clayton Zimmerman 9b00db7f29
Feature/create ipp module (#137)
* Changes grab to return *ScanResults. Implements ippInContentType correctly.

* Slots in an operational re-working of several HTTP module functions, and adds dependency on zgrab's http module. Includes some laregly copy-pasted sections worthy of scrutiny.

* Adds support to retry failed HTTP over HTTPS. Removes vestigial functions.

* Implements sending CUPS-get-printers request if CUPS is detected, yielding more detailed & accurate version information. Also handles URI's more correctly.

* Creates separate container to run IPP over TLS on CUPS. Runs basic tests against both containers.

* Creates virtual printer on each container to test for augmenting data with CUPS-get-printers request (which only works when printers exist).

* Augments version information with CUPS-get-printers response if possible.

* Allows specifying IPP version in constructed requests. Checks for version-not-supported server error.

* Allows resending IPP requests with different versions if we hit a version-not-supported error.

* Updates IPP zgrab2 schema to include fields added in modules/ipp/scanner.go

* Removes unnecessary TODO's

* Updates testable example for new definition of AttributeByteString

* Removes versionNotSupported's dependency on bufferFromBody. Checks bounds on generated requests' fields correctly.

* Updates zgrab2 IPP schema to match ScanResults object in modules/ipp/scanner.go

* Corrects IPP tests, bounds checking, zgrab schema formatting.

* Logs errors for unexpected behavior in buffer io operations. Updates schema to include standalone fields for attributes described in CUPS-get-printers response.

* Logs at debug level only when verbose flag is set. Prints accurate error message when CUPSVersion test fails.

* Handles HTTP request errors before checking for nil response/body. Fixes and tests convertURIToIPP.
2018-06-26 12:00:27 -04:00

136 lines
4.8 KiB
Go

package ipp
import (
"bytes"
"encoding/binary"
"errors"
"math"
"strings"
"net/url"
log "github.com/sirupsen/logrus"
)
// Writes an "attribute-with-one-value" with the provided "value-tag", "name", and "value" to provided buffer
// attribute-with-one-value encoding described at https://tools.ietf.org/html/rfc8010#section-3.1.4
// Example (runnable from ipp_test.go):
// Input: 0x47, "attributes-charset", "us-ascii"
// Output: [71 0 18 97 116 116 114 105 98 117 116 101 115 45 99 104 97 114 115 101 116 0 8 117 115 45 97 115 99 105 105]
// TODO: Switch output and Example function to use hex.Dump()
// TODO: Should return an error when fed an invalid valueTag
func AttributeByteString(valueTag byte, name string, value string, target *bytes.Buffer) error {
//special byte denoting value syntax
binary.Write(target, binary.BigEndian, valueTag)
if len(name) <= math.MaxInt16 && len(name) >= 0 {
//append 16-bit signed int denoting name length
binary.Write(target, binary.BigEndian, int16(len(name)))
//append name
binary.Write(target, binary.BigEndian, []byte(name))
} else {
// TODO: Log error somewhere
return errors.New("Name wrong length to encode.")
}
if len(value) <= math.MaxInt16 && len(value) >= 0 {
//append 16-bit signed int denoting value length
binary.Write(target, binary.BigEndian, int16(len(value)))
//append value
binary.Write(target, binary.BigEndian, []byte(value))
} else {
// TODO: Log error somewhere
return errors.New("Value wrong length to encode.")
}
return nil
}
// TODO: Eventually handle scheme-less urls, even though getHTTPURL will never construct one (we can use regex)
// TODO: RFC claims that literal IP addresses are not valid IPP uri's, but Wireshark IPP Capture example uses them
// (Source: https://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=view&target=ipp.pcap)
func ConvertURIToIPP(uriString string, tls bool) string {
uri, err := url.Parse(uriString)
if err != nil {
log.WithFields(log.Fields{
"error": err,
"url": uriString,
}).Debug("Failed to parse URL from string")
}
// TODO: Create a better condition than uri.Scheme == "" b/c url.Parse doesn't know whether there's a scheme
if uri.Scheme == "" || uri.Scheme == "http" || uri.Scheme == "https" {
if tls {
uri.Scheme = "ipps"
} else {
uri.Scheme = "ipp"
}
}
if !strings.Contains(uri.Host, ":") {
uri.Host += ":631"
}
return uri.String()
}
func getPrintersRequest(major, minor int8) *bytes.Buffer {
var b bytes.Buffer
// Sending too new a version leads to a version-not-supported error, so we'll just send newest
//version
b.Write([]byte{byte(major), byte(minor)})
//operation-id = get-printer-attributes
b.Write([]byte{0x40, 2})
//request-id = 1
b.Write([]byte{0, 0, 0, 1})
//operation-attributes-tag = 1 (begins an attribute-group)
b.Write([]byte{1})
// TODO: Handle error ocurring in any AttributeByteString call
//attributes-charset
AttributeByteString(0x47, "attributes-charset", "utf-8", &b)
//attributes-natural-language
AttributeByteString(0x48, "attributes-natural-language", "en-us", &b)
//end-of-attributes-tag = 3
b.Write([]byte{3})
return &b
}
// TODO: Store everything except uri statically?
// Construct a minimal request that an IPP server will respond to
// IPP request encoding described at https://tools.ietf.org/html/rfc8010#section-3.1.1
func getPrinterAttributesRequest(major, minor int8, uri string, tls bool) *bytes.Buffer {
var b bytes.Buffer
// Using newest version number, because we must provide a supported major version number
// Object must reply to unsupported major version with
// "'server-error-version-not-supported' along with the closest version number that
// is supported" RFC 8011 4.1.8 https://tools.ietf.org/html/rfc8011#4.1.8
// "In all cases, the IPP object MUST return the "version-number" value that it supports
// that is closest to the version number supplied by the Client in the request."
// CUPS behavior defies the RFC. The response to a request with a bad version number should encode
// the closest supported version number per RFC 8011 Section Appendix B.1.5.4 https://tools.ietf.org/html/rfc8011#appendix-B.1.5.4
//version
b.Write([]byte{byte(major), byte(minor)})
//operation-id = get-printer-attributes
b.Write([]byte{0, 0xb})
//request-id = 1
b.Write([]byte{0, 0, 0, 1})
//operation-attributes-tag = 1 (begins an attribute-group)
b.Write([]byte{1})
// TODO: Handle error ocurring in any AttributeByteString call
//attributes-charset
AttributeByteString(0x47, "attributes-charset", "utf-8", &b)
//attributes-natural-language
AttributeByteString(0x48, "attributes-natural-language", "en-us", &b)
//printer-uri
AttributeByteString(0x45, "printer-uri", ConvertURIToIPP(uri, tls), &b)
//requested-attributes
AttributeByteString(0x44, "requested-attributes", "all", &b)
//end-of-attributes-tag = 3
b.Write([]byte{3})
return &b
}