summaryrefslogtreecommitdiffstats
path: root/scripts/http-exif-spider.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/http-exif-spider.nse')
-rw-r--r--scripts/http-exif-spider.nse539
1 files changed, 539 insertions, 0 deletions
diff --git a/scripts/http-exif-spider.nse b/scripts/http-exif-spider.nse
new file mode 100644
index 0000000..ab6a12a
--- /dev/null
+++ b/scripts/http-exif-spider.nse
@@ -0,0 +1,539 @@
+description = [[
+Spiders a site's images looking for interesting exif data embedded in
+.jpg files. Displays the make and model of the camera, the date the photo was
+taken, and the embedded geotag information.
+]]
+
+---
+-- @usage
+-- nmap --script http-exif-spider -p80,443 <host>
+--
+-- @output
+-- PORT STATE SERVICE REASON
+-- 80/tcp open http syn-ack
+-- | http-exif-spider:
+-- | http://www.javaop.com/Nationalmuseum.jpg
+-- | Make: Canon
+-- | Model: Canon PowerShot S100\xB4
+-- | Date: 2003:03:29 13:35:40
+-- | http://www.javaop.com/topleft.jpg
+-- |_ GPS: 49.941250,-97.206189 - https://maps.google.com/maps?q=49.94125,-97.20618863493
+--
+-- @args http-exif-spider.url the url to start spidering. This is a URL
+-- relative to the scanned host eg. /default.html (default: /)
+
+author = "Ron Bowes"
+license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
+categories = {"intrusive"}
+
+local shortport = require 'shortport'
+local stdnse = require 'stdnse'
+local httpspider = require 'httpspider'
+local string = require 'string'
+local table = require 'table'
+
+-- These definitions are copied/pasted/reformatted from the jhead-2.96 sourcecode
+-- (the code is effectively public domain, but credit where credit's due!)
+TAG_INTEROP_INDEX = 0x0001
+TAG_INTEROP_VERSION = 0x0002
+TAG_IMAGE_WIDTH = 0x0100
+TAG_IMAGE_LENGTH = 0x0101
+TAG_BITS_PER_SAMPLE = 0x0102
+TAG_COMPRESSION = 0x0103
+TAG_PHOTOMETRIC_INTERP = 0x0106
+TAG_FILL_ORDER = 0x010A
+TAG_DOCUMENT_NAME = 0x010D
+TAG_IMAGE_DESCRIPTION = 0x010E
+TAG_MAKE = 0x010F
+TAG_MODEL = 0x0110
+TAG_SRIP_OFFSET = 0x0111
+TAG_ORIENTATION = 0x0112
+TAG_SAMPLES_PER_PIXEL = 0x0115
+TAG_ROWS_PER_STRIP = 0x0116
+TAG_STRIP_BYTE_COUNTS = 0x0117
+TAG_X_RESOLUTION = 0x011A
+TAG_Y_RESOLUTION = 0x011B
+TAG_PLANAR_CONFIGURATION = 0x011C
+TAG_RESOLUTION_UNIT = 0x0128
+TAG_TRANSFER_FUNCTION = 0x012D
+TAG_SOFTWARE = 0x0131
+TAG_DATETIME = 0x0132
+TAG_ARTIST = 0x013B
+TAG_WHITE_POINT = 0x013E
+TAG_PRIMARY_CHROMATICITIES = 0x013F
+TAG_TRANSFER_RANGE = 0x0156
+TAG_JPEG_PROC = 0x0200
+TAG_THUMBNAIL_OFFSET = 0x0201
+TAG_THUMBNAIL_LENGTH = 0x0202
+TAG_Y_CB_CR_COEFFICIENTS = 0x0211
+TAG_Y_CB_CR_SUB_SAMPLING = 0x0212
+TAG_Y_CB_CR_POSITIONING = 0x0213
+TAG_REFERENCE_BLACK_WHITE = 0x0214
+TAG_RELATED_IMAGE_WIDTH = 0x1001
+TAG_RELATED_IMAGE_LENGTH = 0x1002
+TAG_CFA_REPEAT_PATTERN_DIM = 0x828D
+TAG_CFA_PATTERN1 = 0x828E
+TAG_BATTERY_LEVEL = 0x828F
+TAG_COPYRIGHT = 0x8298
+TAG_EXPOSURETIME = 0x829A
+TAG_FNUMBER = 0x829D
+TAG_IPTC_NAA = 0x83BB
+TAG_EXIF_OFFSET = 0x8769
+TAG_INTER_COLOR_PROFILE = 0x8773
+TAG_EXPOSURE_PROGRAM = 0x8822
+TAG_SPECTRAL_SENSITIVITY = 0x8824
+TAG_GPSINFO = 0x8825
+TAG_ISO_EQUIVALENT = 0x8827
+TAG_OECF = 0x8828
+TAG_EXIF_VERSION = 0x9000
+TAG_DATETIME_ORIGINAL = 0x9003
+TAG_DATETIME_DIGITIZED = 0x9004
+TAG_COMPONENTS_CONFIG = 0x9101
+TAG_CPRS_BITS_PER_PIXEL = 0x9102
+TAG_SHUTTERSPEED = 0x9201
+TAG_APERTURE = 0x9202
+TAG_BRIGHTNESS_VALUE = 0x9203
+TAG_EXPOSURE_BIAS = 0x9204
+TAG_MAXAPERTURE = 0x9205
+TAG_SUBJECT_DISTANCE = 0x9206
+TAG_METERING_MODE = 0x9207
+TAG_LIGHT_SOURCE = 0x9208
+TAG_FLASH = 0x9209
+TAG_FOCALLENGTH = 0x920A
+TAG_SUBJECTAREA = 0x9214
+TAG_MAKER_NOTE = 0x927C
+TAG_USERCOMMENT = 0x9286
+TAG_SUBSEC_TIME = 0x9290
+TAG_SUBSEC_TIME_ORIG = 0x9291
+TAG_SUBSEC_TIME_DIG = 0x9292
+TAG_WINXP_TITLE = 0x9c9b
+TAG_WINXP_COMMENT = 0x9c9c
+TAG_WINXP_AUTHOR = 0x9c9d
+TAG_WINXP_KEYWORDS = 0x9c9e
+TAG_WINXP_SUBJECT = 0x9c9f
+TAG_FLASH_PIX_VERSION = 0xA000
+TAG_COLOR_SPACE = 0xA001
+TAG_PIXEL_X_DIMENSION = 0xA002
+TAG_PIXEL_Y_DIMENSION = 0xA003
+TAG_RELATED_AUDIO_FILE = 0xA004
+TAG_INTEROP_OFFSET = 0xA005
+TAG_FLASH_ENERGY = 0xA20B
+TAG_SPATIAL_FREQ_RESP = 0xA20C
+TAG_FOCAL_PLANE_XRES = 0xA20E
+TAG_FOCAL_PLANE_YRES = 0xA20F
+TAG_FOCAL_PLANE_UNITS = 0xA210
+TAG_SUBJECT_LOCATION = 0xA214
+TAG_EXPOSURE_INDEX = 0xA215
+TAG_SENSING_METHOD = 0xA217
+TAG_FILE_SOURCE = 0xA300
+TAG_SCENE_TYPE = 0xA301
+TAG_CFA_PATTERN = 0xA302
+TAG_CUSTOM_RENDERED = 0xA401
+TAG_EXPOSURE_MODE = 0xA402
+TAG_WHITEBALANCE = 0xA403
+TAG_DIGITALZOOMRATIO = 0xA404
+TAG_FOCALLENGTH_35MM = 0xA405
+TAG_SCENE_CAPTURE_TYPE = 0xA406
+TAG_GAIN_CONTROL = 0xA407
+TAG_CONTRAST = 0xA408
+TAG_SATURATION = 0xA409
+TAG_SHARPNESS = 0xA40A
+TAG_DISTANCE_RANGE = 0xA40C
+TAG_IMAGE_UNIQUE_ID = 0xA420
+
+TagTable = {}
+TagTable[TAG_INTEROP_INDEX] = "InteropIndex"
+TagTable[TAG_INTEROP_VERSION] = "InteropVersion"
+TagTable[TAG_IMAGE_WIDTH] = "ImageWidth"
+TagTable[TAG_IMAGE_LENGTH] = "ImageLength"
+TagTable[TAG_BITS_PER_SAMPLE] = "BitsPerSample"
+TagTable[TAG_COMPRESSION] = "Compression"
+TagTable[TAG_PHOTOMETRIC_INTERP] = "PhotometricInterpretation"
+TagTable[TAG_FILL_ORDER] = "FillOrder"
+TagTable[TAG_DOCUMENT_NAME] = "DocumentName"
+TagTable[TAG_IMAGE_DESCRIPTION] = "ImageDescription"
+TagTable[TAG_MAKE] = "Make"
+TagTable[TAG_MODEL] = "Model"
+TagTable[TAG_SRIP_OFFSET] = "StripOffsets"
+TagTable[TAG_ORIENTATION] = "Orientation"
+TagTable[TAG_SAMPLES_PER_PIXEL] = "SamplesPerPixel"
+TagTable[TAG_ROWS_PER_STRIP] = "RowsPerStrip"
+TagTable[TAG_STRIP_BYTE_COUNTS] = "StripByteCounts"
+TagTable[TAG_X_RESOLUTION] = "XResolution"
+TagTable[TAG_Y_RESOLUTION] = "YResolution"
+TagTable[TAG_PLANAR_CONFIGURATION] = "PlanarConfiguration"
+TagTable[TAG_RESOLUTION_UNIT] = "ResolutionUnit"
+TagTable[TAG_TRANSFER_FUNCTION] = "TransferFunction"
+TagTable[TAG_SOFTWARE] = "Software"
+TagTable[TAG_DATETIME] = "DateTime"
+TagTable[TAG_ARTIST] = "Artist"
+TagTable[TAG_WHITE_POINT] = "WhitePoint"
+TagTable[TAG_PRIMARY_CHROMATICITIES]= "PrimaryChromaticities"
+TagTable[TAG_TRANSFER_RANGE] = "TransferRange"
+TagTable[TAG_JPEG_PROC] = "JPEGProc"
+TagTable[TAG_THUMBNAIL_OFFSET] = "ThumbnailOffset"
+TagTable[TAG_THUMBNAIL_LENGTH] = "ThumbnailLength"
+TagTable[TAG_Y_CB_CR_COEFFICIENTS] = "YCbCrCoefficients"
+TagTable[TAG_Y_CB_CR_SUB_SAMPLING] = "YCbCrSubSampling"
+TagTable[TAG_Y_CB_CR_POSITIONING] = "YCbCrPositioning"
+TagTable[TAG_REFERENCE_BLACK_WHITE] = "ReferenceBlackWhite"
+TagTable[TAG_RELATED_IMAGE_WIDTH] = "RelatedImageWidth"
+TagTable[TAG_RELATED_IMAGE_LENGTH] = "RelatedImageLength"
+TagTable[TAG_CFA_REPEAT_PATTERN_DIM]= "CFARepeatPatternDim"
+TagTable[TAG_CFA_PATTERN1] = "CFAPattern"
+TagTable[TAG_BATTERY_LEVEL] = "BatteryLevel"
+TagTable[TAG_COPYRIGHT] = "Copyright"
+TagTable[TAG_EXPOSURETIME] = "ExposureTime"
+TagTable[TAG_FNUMBER] = "FNumber"
+TagTable[TAG_IPTC_NAA] = "IPTC/NAA"
+TagTable[TAG_EXIF_OFFSET] = "ExifOffset"
+TagTable[TAG_INTER_COLOR_PROFILE] = "InterColorProfile"
+TagTable[TAG_EXPOSURE_PROGRAM] = "ExposureProgram"
+TagTable[TAG_SPECTRAL_SENSITIVITY] = "SpectralSensitivity"
+TagTable[TAG_GPSINFO] = "GPS Dir offset"
+TagTable[TAG_ISO_EQUIVALENT] = "ISOSpeedRatings"
+TagTable[TAG_OECF] = "OECF"
+TagTable[TAG_EXIF_VERSION] = "ExifVersion"
+TagTable[TAG_DATETIME_ORIGINAL] = "DateTimeOriginal"
+TagTable[TAG_DATETIME_DIGITIZED] = "DateTimeDigitized"
+TagTable[TAG_COMPONENTS_CONFIG] = "ComponentsConfiguration"
+TagTable[TAG_CPRS_BITS_PER_PIXEL] = "CompressedBitsPerPixel"
+TagTable[TAG_SHUTTERSPEED] = "ShutterSpeedValue"
+TagTable[TAG_APERTURE] = "ApertureValue"
+TagTable[TAG_BRIGHTNESS_VALUE] = "BrightnessValue"
+TagTable[TAG_EXPOSURE_BIAS] = "ExposureBiasValue"
+TagTable[TAG_MAXAPERTURE] = "MaxApertureValue"
+TagTable[TAG_SUBJECT_DISTANCE] = "SubjectDistance"
+TagTable[TAG_METERING_MODE] = "MeteringMode"
+TagTable[TAG_LIGHT_SOURCE] = "LightSource"
+TagTable[TAG_FLASH] = "Flash"
+TagTable[TAG_FOCALLENGTH] = "FocalLength"
+TagTable[TAG_MAKER_NOTE] = "MakerNote"
+TagTable[TAG_USERCOMMENT] = "UserComment"
+TagTable[TAG_SUBSEC_TIME] = "SubSecTime"
+TagTable[TAG_SUBSEC_TIME_ORIG] = "SubSecTimeOriginal"
+TagTable[TAG_SUBSEC_TIME_DIG] = "SubSecTimeDigitized"
+TagTable[TAG_WINXP_TITLE] = "Windows-XP Title"
+TagTable[TAG_WINXP_COMMENT] = "Windows-XP comment"
+TagTable[TAG_WINXP_AUTHOR] = "Windows-XP author"
+TagTable[TAG_WINXP_KEYWORDS] = "Windows-XP keywords"
+TagTable[TAG_WINXP_SUBJECT] = "Windows-XP subject"
+TagTable[TAG_FLASH_PIX_VERSION] = "FlashPixVersion"
+TagTable[TAG_COLOR_SPACE] = "ColorSpace"
+TagTable[TAG_PIXEL_X_DIMENSION] = "ExifImageWidth"
+TagTable[TAG_PIXEL_Y_DIMENSION] = "ExifImageLength"
+TagTable[TAG_RELATED_AUDIO_FILE] = "RelatedAudioFile"
+TagTable[TAG_INTEROP_OFFSET] = "InteroperabilityOffset"
+TagTable[TAG_FLASH_ENERGY] = "FlashEnergy"
+TagTable[TAG_SPATIAL_FREQ_RESP] = "SpatialFrequencyResponse"
+TagTable[TAG_FOCAL_PLANE_XRES] = "FocalPlaneXResolution"
+TagTable[TAG_FOCAL_PLANE_YRES] = "FocalPlaneYResolution"
+TagTable[TAG_FOCAL_PLANE_UNITS] = "FocalPlaneResolutionUnit"
+TagTable[TAG_SUBJECT_LOCATION] = "SubjectLocation"
+TagTable[TAG_EXPOSURE_INDEX] = "ExposureIndex"
+TagTable[TAG_SENSING_METHOD] = "SensingMethod"
+TagTable[TAG_FILE_SOURCE] = "FileSource"
+TagTable[TAG_SCENE_TYPE] = "SceneType"
+TagTable[TAG_CFA_PATTERN] = "CFA Pattern"
+TagTable[TAG_CUSTOM_RENDERED] = "CustomRendered"
+TagTable[TAG_EXPOSURE_MODE] = "ExposureMode"
+TagTable[TAG_WHITEBALANCE] = "WhiteBalance"
+TagTable[TAG_DIGITALZOOMRATIO] = "DigitalZoomRatio"
+TagTable[TAG_FOCALLENGTH_35MM] = "FocalLengthIn35mmFilm"
+TagTable[TAG_SUBJECTAREA] = "SubjectArea"
+TagTable[TAG_SCENE_CAPTURE_TYPE] = "SceneCaptureType"
+TagTable[TAG_GAIN_CONTROL] = "GainControl"
+TagTable[TAG_CONTRAST] = "Contrast"
+TagTable[TAG_SATURATION] = "Saturation"
+TagTable[TAG_SHARPNESS] = "Sharpness"
+TagTable[TAG_DISTANCE_RANGE] = "SubjectDistanceRange"
+TagTable[TAG_IMAGE_UNIQUE_ID] = "ImageUniqueId"
+
+GPS_TAG_VERSIONID = 0X00
+GPS_TAG_LATITUDEREF = 0X01
+GPS_TAG_LATITUDE = 0X02
+GPS_TAG_LONGITUDEREF = 0X03
+GPS_TAG_LONGITUDE = 0X04
+GPS_TAG_ALTITUDEREF = 0X05
+GPS_TAG_ALTITUDE = 0X06
+GPS_TAG_TIMESTAMP = 0X07
+GPS_TAG_SATELLITES = 0X08
+GPS_TAG_STATUS = 0X09
+GPS_TAG_MEASUREMODE = 0X0A
+GPS_TAG_DOP = 0X0B
+GPS_TAG_SPEEDREF = 0X0C
+GPS_TAG_SPEED = 0X0D
+GPS_TAG_TRACKREF = 0X0E
+GPS_TAG_TRACK = 0X0F
+GPS_TAG_IMGDIRECTIONREF = 0X10
+GPS_TAG_IMGDIRECTION = 0X11
+GPS_TAG_MAPDATUM = 0X12
+GPS_TAG_DESTLATITUDEREF = 0X13
+GPS_TAG_DESTLATITUDE = 0X14
+GPS_TAG_DESTLONGITUDEREF = 0X15
+GPS_TAG_DESTLONGITUDE = 0X16
+GPS_TAG_DESTBEARINGREF = 0X17
+GPS_TAG_DESTBEARING = 0X18
+GPS_TAG_DESTDISTANCEREF = 0X19
+GPS_TAG_DESTDISTANCE = 0X1A
+GPS_TAG_PROCESSINGMETHOD = 0X1B
+GPS_TAG_AREAINFORMATION = 0X1C
+GPS_TAG_DATESTAMP = 0X1D
+GPS_TAG_DIFFERENTIAL = 0X1E
+
+GpsTagTable = {}
+GpsTagTable[GPS_TAG_VERSIONID] = "VersionID"
+GpsTagTable[GPS_TAG_LATITUDEREF] = "LatitudeRef"
+GpsTagTable[GPS_TAG_LATITUDE] = "Latitude"
+GpsTagTable[GPS_TAG_LONGITUDEREF] = "LongitudeRef"
+GpsTagTable[GPS_TAG_LONGITUDE] = "Longitude"
+GpsTagTable[GPS_TAG_ALTITUDEREF] = "AltitudeRef"
+GpsTagTable[GPS_TAG_ALTITUDE] = "Altitude"
+GpsTagTable[GPS_TAG_TIMESTAMP] = "Timestamp"
+GpsTagTable[GPS_TAG_SATELLITES] = "Satellites"
+GpsTagTable[GPS_TAG_STATUS] = "Status"
+GpsTagTable[GPS_TAG_MEASUREMODE] = "MeasureMode"
+GpsTagTable[GPS_TAG_DOP] = "Dop"
+GpsTagTable[GPS_TAG_SPEEDREF] = "SpeedRef"
+GpsTagTable[GPS_TAG_SPEED] = "Speed"
+GpsTagTable[GPS_TAG_TRACKREF] = "TrafRef"
+GpsTagTable[GPS_TAG_TRACK] = "Track"
+GpsTagTable[GPS_TAG_IMGDIRECTIONREF] = "ImgDirectionRef"
+GpsTagTable[GPS_TAG_IMGDIRECTION] = "ImgDirection"
+GpsTagTable[GPS_TAG_MAPDATUM] = "MapDatum"
+GpsTagTable[GPS_TAG_DESTLATITUDEREF] = "DestLatitudeRef"
+GpsTagTable[GPS_TAG_DESTLATITUDE] = "DestLatitude"
+GpsTagTable[GPS_TAG_DESTLONGITUDEREF]= "DestLongitudeRef"
+GpsTagTable[GPS_TAG_DESTLONGITUDE] = "DestLongitude"
+GpsTagTable[GPS_TAG_DESTBEARINGREF] = "DestBearingref"
+GpsTagTable[GPS_TAG_DESTBEARING] = "DestBearing"
+GpsTagTable[GPS_TAG_DESTDISTANCEREF] = "DestDistanceRef"
+GpsTagTable[GPS_TAG_DESTDISTANCE] = "DestDistance"
+GpsTagTable[GPS_TAG_PROCESSINGMETHOD]= "ProcessingMethod"
+GpsTagTable[GPS_TAG_AREAINFORMATION] = "AreaInformation"
+GpsTagTable[GPS_TAG_DATESTAMP] = "Datestamp"
+GpsTagTable[GPS_TAG_DIFFERENTIAL] = "Differential"
+
+FMT_BYTE = 1
+FMT_STRING = 2
+FMT_USHORT = 3
+FMT_ULONG = 4
+FMT_URATIONAL = 5
+FMT_SBYTE = 6
+FMT_UNDEFINED = 7
+FMT_SSHORT = 8
+FMT_SLONG = 9
+FMT_SRATIONAL = 10
+FMT_SINGLE = 11
+FMT_DOUBLE = 12
+
+bytes_per_format = {0,1,1,2,4,8,1,1,2,4,8,4,8}
+
+portrule = shortport.http
+
+---Unpack a rational number from exif. In exif, a rational number is stored
+--as a pair of integers - the numerator and the denominator.
+--
+--@return the new position, and the value.
+local function unpack_rational(endian, data, pos)
+ local v1, v2
+ v1, v2, pos = string.unpack(endian .. "I4I4", data, pos)
+ return pos, v1 / v2
+end
+
+local function process_gps(data, pos, endian, result)
+ local value, num_entries
+ local latitude, latitude_ref, longitude, longitude_ref
+
+ -- The first entry in the gps section is a 16-bit size
+ num_entries, pos = string.unpack(endian .. "I2", data, pos)
+
+ -- Loop through the entries to find the fun stuff
+ for i=1, num_entries do
+ local tag, format, components, value
+ tag, format, components, value, pos = string.unpack(endian .. "I2 I2 I4 I4", data, pos)
+
+ if(tag == GPS_TAG_LATITUDE or tag == GPS_TAG_LONGITUDE) then
+ local dummy, gps, h, m, s
+ dummy, h = unpack_rational(endian, data, value + 8)
+ dummy, m = unpack_rational(endian, data, dummy)
+ dummy, s = unpack_rational(endian, data, dummy)
+
+ gps = h + (m / 60) + (s / 60 / 60)
+
+ if(tag == GPS_TAG_LATITUDE) then
+ latitude = gps
+ else
+ longitude = gps
+ end
+ elseif(tag == GPS_TAG_LATITUDEREF) then
+ -- Get the first byte in the latitude reference as a character
+ latitude_ref = string.char(value >> 24)
+ elseif(tag == GPS_TAG_LONGITUDEREF) then
+ -- Get the first byte in the longitude reference as a character
+ longitude_ref = string.char(value >> 24)
+ end
+ end
+
+ if(latitude and longitude) then
+ -- Normalize the N/S/E/W to positive and negative
+ if(latitude_ref == 'S') then
+ latitude = -latitude
+ end
+ if(longitude_ref == 'W') then
+ longitude = -longitude
+ end
+
+ table.insert(result, string.format("GPS: %f,%f - https://maps.google.com/maps?q=%s,%s", latitude, longitude, latitude, longitude))
+ end
+
+ return true, result
+end
+
+---Parse the exif data section and return a table. This has only been tested
+--in a .jpeg file, but should work for .tiff as well.
+local function parse_exif(exif_data)
+ local sig, marker, size
+ local tag, format, components, byte_count, value, offset, dummy, data
+ local status, result
+ local tiff_header_1, first_offset
+
+ -- Initialize the result table
+ result = {}
+
+ -- Read the verify the EXIF header
+ local header, endian, pos = string.unpack(">c6 I2", exif_data, 1)
+ if(header ~= "Exif\0\0") then
+ return false, "Invalid EXIF header"
+ end
+
+ -- Check the endianness - it should only ever be big endian, but it doesn't
+ -- hurt to check
+ if(endian == 0x4d4d) then
+ endian = ">"
+ elseif(endian == 0x4949) then
+ endian = "<"
+ else
+ return false, "Unrecognized endianness entry"
+ end
+
+ -- Read the first tiff header and the offset to the first data entry (should be 8)
+ tiff_header_1, first_offset, pos = string.unpack(endian .. "I2 I4", exif_data, pos)
+ if(tiff_header_1 ~= 0x002A or first_offset ~= 0x00000008) then
+ return false, "Invalid tiff header"
+ end
+
+ -- Skip over the header, and go to the first offset (subtracting 1 because lua)
+ pos = first_offset + 8 - 1
+
+ -- The first 16-bit value is the number of entries
+ local num_entries, pos = string.unpack(endian .. "I2", exif_data, pos)
+
+ -- Loop through the entries
+ for i=1,num_entries do
+ -- Read the entry's header
+ tag, format, components, value, pos = string.unpack(endian .. "I2 I2 I4 I4", exif_data, pos)
+
+ -- Look at the tags we care about
+ if(tag == TAG_GPSINFO) then
+ -- If it's a GPSINFO tag, we need to parse the GPS structure
+ status, result = process_gps(exif_data, value + 8 - 1, endian, result)
+ if(not(status)) then
+ return false, result
+ end
+ else
+ value = string.unpack("z", exif_data, value + 8 - 1)
+ if (tag == TAG_MAKE) then
+ table.insert(result, string.format("Make: %s", value))
+ elseif(tag == TAG_MODEL) then
+ table.insert(result, string.format("Model: %s", value))
+ elseif(tag == TAG_DATETIME) then
+ table.insert(result, string.format("Date: %s", value))
+ end
+ end
+ end
+
+ return true, result
+end
+
+---Parse a jpeg and find the EXIF data section
+local function parse_jpeg(s)
+ local pos, sig, marker, size, exif_data
+
+ -- Parse the jpeg header, make sure it's valid (we expect 0xFFD8)
+ sig, pos = string.unpack(">I2", s, pos)
+ if(sig ~= 0xFFD8) then
+ return false, "Unexpected signature"
+ end
+
+ -- Parse the sections to find the exif marker (0xffe1)
+ while(true) do
+ marker, size, pos = string.unpack(">I2I2", s, pos)
+
+ -- Check if we found the exif metadata section, break if we did
+ if(marker == 0xffe1) then
+ break
+ -- If the marker is nil, we're off the end of the image (and therefore, it wasn't found)
+ elseif(not(marker)) then
+ return false, "Could not found EXIF marker"
+ end
+
+ -- Go to the next section (we subtract 2 because of the 2-byte marker we read)
+ pos = pos + size - 2
+ end
+
+ exif_data, pos = string.unpack(string.format(">c%d", size), s, pos)
+
+ return parse_exif(exif_data)
+end
+
+
+function action(host, port)
+ local pattern = "%.jpg"
+ local images = {}
+ local results = {}
+
+ -- once we know the pattern we'll be searching for, we can set up the function
+ local whitelist = function(url)
+ return string.match(url.file, "%.jpg") or string.match(url.file, "%.jpeg")
+ end
+
+ local crawler = httpspider.Crawler:new( host, port, nil, { scriptname = SCRIPT_NAME, whitelist = { whitelist }} )
+
+ if ( not(crawler) ) then
+ return
+ end
+
+ while(true) do
+ -- Begin the crawler
+ local status, r = crawler:crawl()
+
+ -- Make sure there's no error
+ if ( not(status) ) then
+ if ( r.err ) then
+ return stdnse.format_output(false, r.reason)
+ else
+ break
+ end
+ end
+
+ -- Check if we got a response, and the response is a .jpg file
+ if r.response and r.response.body and r.response.status==200 and (string.match(r.url.path, ".jpg") or string.match(r.url.path, ".jpeg")) then
+ local status, result
+ stdnse.debug1("Attempting to read exif data from %s", r.url.raw)
+ status, result = parse_jpeg(r.response.body)
+ if(not(status)) then
+ stdnse.debug1("Couldn't read exif from %s: %s", r.url.raw, result)
+ else
+ -- If there are any exif results, add them to the result
+ if(result and #result > 0) then
+ result['name'] = r.url.raw
+ table.insert(results, result)
+ end
+ end
+ end
+ end
+
+ return stdnse.format_output(true, results)
+end
+