1description = [[
2Spiders a site's images looking for interesting exif data embedded in
3.jpg files. Displays the make and model of the camera, the date the photo was
4taken, and the embedded geotag information.
5]]
6
7---
8-- @usage
9-- nmap --script http-exif-spider -p80,443 <host>
10--
11-- @output
12-- PORT   STATE SERVICE REASON
13-- 80/tcp open  http    syn-ack
14-- | http-exif-spider:
15-- |   http://www.javaop.com/Nationalmuseum.jpg
16-- |     Make: Canon
17-- |     Model: Canon PowerShot S100\xB4
18-- |     Date: 2003:03:29 13:35:40
19-- |   http://www.javaop.com/topleft.jpg
20-- |_    GPS: 49.941250,-97.206189 - https://maps.google.com/maps?q=49.94125,-97.20618863493
21--
22-- @args http-exif-spider.url the url to start spidering. This is a URL
23-- relative to the scanned host eg. /default.html (default: /)
24
25author = "Ron Bowes"
26license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
27categories = {"intrusive"}
28
29local shortport = require 'shortport'
30local stdnse = require 'stdnse'
31local httpspider = require 'httpspider'
32local string = require 'string'
33local table = require 'table'
34
35-- These definitions are copied/pasted/reformatted from the jhead-2.96 sourcecode
36-- (the code is effectively public domain, but credit where credit's due!)
37TAG_INTEROP_INDEX          = 0x0001
38TAG_INTEROP_VERSION        = 0x0002
39TAG_IMAGE_WIDTH            = 0x0100
40TAG_IMAGE_LENGTH           = 0x0101
41TAG_BITS_PER_SAMPLE        = 0x0102
42TAG_COMPRESSION            = 0x0103
43TAG_PHOTOMETRIC_INTERP     = 0x0106
44TAG_FILL_ORDER             = 0x010A
45TAG_DOCUMENT_NAME          = 0x010D
46TAG_IMAGE_DESCRIPTION      = 0x010E
47TAG_MAKE                   = 0x010F
48TAG_MODEL                  = 0x0110
49TAG_SRIP_OFFSET            = 0x0111
50TAG_ORIENTATION            = 0x0112
51TAG_SAMPLES_PER_PIXEL      = 0x0115
52TAG_ROWS_PER_STRIP         = 0x0116
53TAG_STRIP_BYTE_COUNTS      = 0x0117
54TAG_X_RESOLUTION           = 0x011A
55TAG_Y_RESOLUTION           = 0x011B
56TAG_PLANAR_CONFIGURATION   = 0x011C
57TAG_RESOLUTION_UNIT        = 0x0128
58TAG_TRANSFER_FUNCTION      = 0x012D
59TAG_SOFTWARE               = 0x0131
60TAG_DATETIME               = 0x0132
61TAG_ARTIST                 = 0x013B
62TAG_WHITE_POINT            = 0x013E
63TAG_PRIMARY_CHROMATICITIES = 0x013F
64TAG_TRANSFER_RANGE         = 0x0156
65TAG_JPEG_PROC              = 0x0200
66TAG_THUMBNAIL_OFFSET       = 0x0201
67TAG_THUMBNAIL_LENGTH       = 0x0202
68TAG_Y_CB_CR_COEFFICIENTS   = 0x0211
69TAG_Y_CB_CR_SUB_SAMPLING   = 0x0212
70TAG_Y_CB_CR_POSITIONING    = 0x0213
71TAG_REFERENCE_BLACK_WHITE  = 0x0214
72TAG_RELATED_IMAGE_WIDTH    = 0x1001
73TAG_RELATED_IMAGE_LENGTH   = 0x1002
74TAG_CFA_REPEAT_PATTERN_DIM = 0x828D
75TAG_CFA_PATTERN1           = 0x828E
76TAG_BATTERY_LEVEL          = 0x828F
77TAG_COPYRIGHT              = 0x8298
78TAG_EXPOSURETIME           = 0x829A
79TAG_FNUMBER                = 0x829D
80TAG_IPTC_NAA               = 0x83BB
81TAG_EXIF_OFFSET            = 0x8769
82TAG_INTER_COLOR_PROFILE    = 0x8773
83TAG_EXPOSURE_PROGRAM       = 0x8822
84TAG_SPECTRAL_SENSITIVITY   = 0x8824
85TAG_GPSINFO                = 0x8825
86TAG_ISO_EQUIVALENT         = 0x8827
87TAG_OECF                   = 0x8828
88TAG_EXIF_VERSION           = 0x9000
89TAG_DATETIME_ORIGINAL      = 0x9003
90TAG_DATETIME_DIGITIZED     = 0x9004
91TAG_COMPONENTS_CONFIG      = 0x9101
92TAG_CPRS_BITS_PER_PIXEL    = 0x9102
93TAG_SHUTTERSPEED           = 0x9201
94TAG_APERTURE               = 0x9202
95TAG_BRIGHTNESS_VALUE       = 0x9203
96TAG_EXPOSURE_BIAS          = 0x9204
97TAG_MAXAPERTURE            = 0x9205
98TAG_SUBJECT_DISTANCE       = 0x9206
99TAG_METERING_MODE          = 0x9207
100TAG_LIGHT_SOURCE           = 0x9208
101TAG_FLASH                  = 0x9209
102TAG_FOCALLENGTH            = 0x920A
103TAG_SUBJECTAREA            = 0x9214
104TAG_MAKER_NOTE             = 0x927C
105TAG_USERCOMMENT            = 0x9286
106TAG_SUBSEC_TIME            = 0x9290
107TAG_SUBSEC_TIME_ORIG       = 0x9291
108TAG_SUBSEC_TIME_DIG        = 0x9292
109TAG_WINXP_TITLE            = 0x9c9b
110TAG_WINXP_COMMENT          = 0x9c9c
111TAG_WINXP_AUTHOR           = 0x9c9d
112TAG_WINXP_KEYWORDS         = 0x9c9e
113TAG_WINXP_SUBJECT          = 0x9c9f
114TAG_FLASH_PIX_VERSION      = 0xA000
115TAG_COLOR_SPACE            = 0xA001
116TAG_PIXEL_X_DIMENSION      = 0xA002
117TAG_PIXEL_Y_DIMENSION      = 0xA003
118TAG_RELATED_AUDIO_FILE     = 0xA004
119TAG_INTEROP_OFFSET         = 0xA005
120TAG_FLASH_ENERGY           = 0xA20B
121TAG_SPATIAL_FREQ_RESP      = 0xA20C
122TAG_FOCAL_PLANE_XRES       = 0xA20E
123TAG_FOCAL_PLANE_YRES       = 0xA20F
124TAG_FOCAL_PLANE_UNITS      = 0xA210
125TAG_SUBJECT_LOCATION       = 0xA214
126TAG_EXPOSURE_INDEX         = 0xA215
127TAG_SENSING_METHOD         = 0xA217
128TAG_FILE_SOURCE            = 0xA300
129TAG_SCENE_TYPE             = 0xA301
130TAG_CFA_PATTERN            = 0xA302
131TAG_CUSTOM_RENDERED        = 0xA401
132TAG_EXPOSURE_MODE          = 0xA402
133TAG_WHITEBALANCE           = 0xA403
134TAG_DIGITALZOOMRATIO       = 0xA404
135TAG_FOCALLENGTH_35MM       = 0xA405
136TAG_SCENE_CAPTURE_TYPE     = 0xA406
137TAG_GAIN_CONTROL           = 0xA407
138TAG_CONTRAST               = 0xA408
139TAG_SATURATION             = 0xA409
140TAG_SHARPNESS              = 0xA40A
141TAG_DISTANCE_RANGE         = 0xA40C
142TAG_IMAGE_UNIQUE_ID        = 0xA420
143
144TagTable = {}
145TagTable[TAG_INTEROP_INDEX]         = "InteropIndex"
146TagTable[TAG_INTEROP_VERSION]       = "InteropVersion"
147TagTable[TAG_IMAGE_WIDTH]           = "ImageWidth"
148TagTable[TAG_IMAGE_LENGTH]          = "ImageLength"
149TagTable[TAG_BITS_PER_SAMPLE]       = "BitsPerSample"
150TagTable[TAG_COMPRESSION]           = "Compression"
151TagTable[TAG_PHOTOMETRIC_INTERP]    = "PhotometricInterpretation"
152TagTable[TAG_FILL_ORDER]            = "FillOrder"
153TagTable[TAG_DOCUMENT_NAME]         = "DocumentName"
154TagTable[TAG_IMAGE_DESCRIPTION]     = "ImageDescription"
155TagTable[TAG_MAKE]                  = "Make"
156TagTable[TAG_MODEL]                 = "Model"
157TagTable[TAG_SRIP_OFFSET]           = "StripOffsets"
158TagTable[TAG_ORIENTATION]           = "Orientation"
159TagTable[TAG_SAMPLES_PER_PIXEL]     = "SamplesPerPixel"
160TagTable[TAG_ROWS_PER_STRIP]        = "RowsPerStrip"
161TagTable[TAG_STRIP_BYTE_COUNTS]     = "StripByteCounts"
162TagTable[TAG_X_RESOLUTION]          = "XResolution"
163TagTable[TAG_Y_RESOLUTION]          = "YResolution"
164TagTable[TAG_PLANAR_CONFIGURATION]  = "PlanarConfiguration"
165TagTable[TAG_RESOLUTION_UNIT]       = "ResolutionUnit"
166TagTable[TAG_TRANSFER_FUNCTION]     = "TransferFunction"
167TagTable[TAG_SOFTWARE]              = "Software"
168TagTable[TAG_DATETIME]              = "DateTime"
169TagTable[TAG_ARTIST]                = "Artist"
170TagTable[TAG_WHITE_POINT]           = "WhitePoint"
171TagTable[TAG_PRIMARY_CHROMATICITIES]= "PrimaryChromaticities"
172TagTable[TAG_TRANSFER_RANGE]        = "TransferRange"
173TagTable[TAG_JPEG_PROC]             = "JPEGProc"
174TagTable[TAG_THUMBNAIL_OFFSET]      = "ThumbnailOffset"
175TagTable[TAG_THUMBNAIL_LENGTH]      = "ThumbnailLength"
176TagTable[TAG_Y_CB_CR_COEFFICIENTS]  = "YCbCrCoefficients"
177TagTable[TAG_Y_CB_CR_SUB_SAMPLING]  = "YCbCrSubSampling"
178TagTable[TAG_Y_CB_CR_POSITIONING]   = "YCbCrPositioning"
179TagTable[TAG_REFERENCE_BLACK_WHITE] = "ReferenceBlackWhite"
180TagTable[TAG_RELATED_IMAGE_WIDTH]   = "RelatedImageWidth"
181TagTable[TAG_RELATED_IMAGE_LENGTH]  = "RelatedImageLength"
182TagTable[TAG_CFA_REPEAT_PATTERN_DIM]= "CFARepeatPatternDim"
183TagTable[TAG_CFA_PATTERN1]          = "CFAPattern"
184TagTable[TAG_BATTERY_LEVEL]         = "BatteryLevel"
185TagTable[TAG_COPYRIGHT]             = "Copyright"
186TagTable[TAG_EXPOSURETIME]          = "ExposureTime"
187TagTable[TAG_FNUMBER]               = "FNumber"
188TagTable[TAG_IPTC_NAA]              = "IPTC/NAA"
189TagTable[TAG_EXIF_OFFSET]           = "ExifOffset"
190TagTable[TAG_INTER_COLOR_PROFILE]   = "InterColorProfile"
191TagTable[TAG_EXPOSURE_PROGRAM]      = "ExposureProgram"
192TagTable[TAG_SPECTRAL_SENSITIVITY]  = "SpectralSensitivity"
193TagTable[TAG_GPSINFO]               = "GPS Dir offset"
194TagTable[TAG_ISO_EQUIVALENT]        = "ISOSpeedRatings"
195TagTable[TAG_OECF]                  = "OECF"
196TagTable[TAG_EXIF_VERSION]          = "ExifVersion"
197TagTable[TAG_DATETIME_ORIGINAL]     = "DateTimeOriginal"
198TagTable[TAG_DATETIME_DIGITIZED]    = "DateTimeDigitized"
199TagTable[TAG_COMPONENTS_CONFIG]     = "ComponentsConfiguration"
200TagTable[TAG_CPRS_BITS_PER_PIXEL]   = "CompressedBitsPerPixel"
201TagTable[TAG_SHUTTERSPEED]          = "ShutterSpeedValue"
202TagTable[TAG_APERTURE]              = "ApertureValue"
203TagTable[TAG_BRIGHTNESS_VALUE]      = "BrightnessValue"
204TagTable[TAG_EXPOSURE_BIAS]         = "ExposureBiasValue"
205TagTable[TAG_MAXAPERTURE]           = "MaxApertureValue"
206TagTable[TAG_SUBJECT_DISTANCE]      = "SubjectDistance"
207TagTable[TAG_METERING_MODE]         = "MeteringMode"
208TagTable[TAG_LIGHT_SOURCE]          = "LightSource"
209TagTable[TAG_FLASH]                 = "Flash"
210TagTable[TAG_FOCALLENGTH]           = "FocalLength"
211TagTable[TAG_MAKER_NOTE]            = "MakerNote"
212TagTable[TAG_USERCOMMENT]           = "UserComment"
213TagTable[TAG_SUBSEC_TIME]           = "SubSecTime"
214TagTable[TAG_SUBSEC_TIME_ORIG]      = "SubSecTimeOriginal"
215TagTable[TAG_SUBSEC_TIME_DIG]       = "SubSecTimeDigitized"
216TagTable[TAG_WINXP_TITLE]           = "Windows-XP Title"
217TagTable[TAG_WINXP_COMMENT]         = "Windows-XP comment"
218TagTable[TAG_WINXP_AUTHOR]          = "Windows-XP author"
219TagTable[TAG_WINXP_KEYWORDS]        = "Windows-XP keywords"
220TagTable[TAG_WINXP_SUBJECT]         = "Windows-XP subject"
221TagTable[TAG_FLASH_PIX_VERSION]     = "FlashPixVersion"
222TagTable[TAG_COLOR_SPACE]           = "ColorSpace"
223TagTable[TAG_PIXEL_X_DIMENSION]     = "ExifImageWidth"
224TagTable[TAG_PIXEL_Y_DIMENSION]     = "ExifImageLength"
225TagTable[TAG_RELATED_AUDIO_FILE]    = "RelatedAudioFile"
226TagTable[TAG_INTEROP_OFFSET]        = "InteroperabilityOffset"
227TagTable[TAG_FLASH_ENERGY]          = "FlashEnergy"
228TagTable[TAG_SPATIAL_FREQ_RESP]     = "SpatialFrequencyResponse"
229TagTable[TAG_FOCAL_PLANE_XRES]      = "FocalPlaneXResolution"
230TagTable[TAG_FOCAL_PLANE_YRES]      = "FocalPlaneYResolution"
231TagTable[TAG_FOCAL_PLANE_UNITS]     = "FocalPlaneResolutionUnit"
232TagTable[TAG_SUBJECT_LOCATION]      = "SubjectLocation"
233TagTable[TAG_EXPOSURE_INDEX]        = "ExposureIndex"
234TagTable[TAG_SENSING_METHOD]        = "SensingMethod"
235TagTable[TAG_FILE_SOURCE]           = "FileSource"
236TagTable[TAG_SCENE_TYPE]            = "SceneType"
237TagTable[TAG_CFA_PATTERN]           = "CFA Pattern"
238TagTable[TAG_CUSTOM_RENDERED]       = "CustomRendered"
239TagTable[TAG_EXPOSURE_MODE]         = "ExposureMode"
240TagTable[TAG_WHITEBALANCE]          = "WhiteBalance"
241TagTable[TAG_DIGITALZOOMRATIO]      = "DigitalZoomRatio"
242TagTable[TAG_FOCALLENGTH_35MM]      = "FocalLengthIn35mmFilm"
243TagTable[TAG_SUBJECTAREA]           = "SubjectArea"
244TagTable[TAG_SCENE_CAPTURE_TYPE]    = "SceneCaptureType"
245TagTable[TAG_GAIN_CONTROL]          = "GainControl"
246TagTable[TAG_CONTRAST]              = "Contrast"
247TagTable[TAG_SATURATION]            = "Saturation"
248TagTable[TAG_SHARPNESS]             = "Sharpness"
249TagTable[TAG_DISTANCE_RANGE]        = "SubjectDistanceRange"
250TagTable[TAG_IMAGE_UNIQUE_ID]       = "ImageUniqueId"
251
252GPS_TAG_VERSIONID        = 0X00
253GPS_TAG_LATITUDEREF      = 0X01
254GPS_TAG_LATITUDE         = 0X02
255GPS_TAG_LONGITUDEREF     = 0X03
256GPS_TAG_LONGITUDE        = 0X04
257GPS_TAG_ALTITUDEREF      = 0X05
258GPS_TAG_ALTITUDE         = 0X06
259GPS_TAG_TIMESTAMP        = 0X07
260GPS_TAG_SATELLITES       = 0X08
261GPS_TAG_STATUS           = 0X09
262GPS_TAG_MEASUREMODE      = 0X0A
263GPS_TAG_DOP              = 0X0B
264GPS_TAG_SPEEDREF         = 0X0C
265GPS_TAG_SPEED            = 0X0D
266GPS_TAG_TRACKREF         = 0X0E
267GPS_TAG_TRACK            = 0X0F
268GPS_TAG_IMGDIRECTIONREF  = 0X10
269GPS_TAG_IMGDIRECTION     = 0X11
270GPS_TAG_MAPDATUM         = 0X12
271GPS_TAG_DESTLATITUDEREF  = 0X13
272GPS_TAG_DESTLATITUDE     = 0X14
273GPS_TAG_DESTLONGITUDEREF = 0X15
274GPS_TAG_DESTLONGITUDE    = 0X16
275GPS_TAG_DESTBEARINGREF   = 0X17
276GPS_TAG_DESTBEARING      = 0X18
277GPS_TAG_DESTDISTANCEREF  = 0X19
278GPS_TAG_DESTDISTANCE     = 0X1A
279GPS_TAG_PROCESSINGMETHOD = 0X1B
280GPS_TAG_AREAINFORMATION  = 0X1C
281GPS_TAG_DATESTAMP        = 0X1D
282GPS_TAG_DIFFERENTIAL     = 0X1E
283
284GpsTagTable = {}
285GpsTagTable[GPS_TAG_VERSIONID]       = "VersionID"
286GpsTagTable[GPS_TAG_LATITUDEREF]     = "LatitudeRef"
287GpsTagTable[GPS_TAG_LATITUDE]        = "Latitude"
288GpsTagTable[GPS_TAG_LONGITUDEREF]    = "LongitudeRef"
289GpsTagTable[GPS_TAG_LONGITUDE]       = "Longitude"
290GpsTagTable[GPS_TAG_ALTITUDEREF]     = "AltitudeRef"
291GpsTagTable[GPS_TAG_ALTITUDE]        = "Altitude"
292GpsTagTable[GPS_TAG_TIMESTAMP]       = "Timestamp"
293GpsTagTable[GPS_TAG_SATELLITES]      = "Satellites"
294GpsTagTable[GPS_TAG_STATUS]          = "Status"
295GpsTagTable[GPS_TAG_MEASUREMODE]     = "MeasureMode"
296GpsTagTable[GPS_TAG_DOP]             = "Dop"
297GpsTagTable[GPS_TAG_SPEEDREF]        = "SpeedRef"
298GpsTagTable[GPS_TAG_SPEED]           = "Speed"
299GpsTagTable[GPS_TAG_TRACKREF]        = "TrafRef"
300GpsTagTable[GPS_TAG_TRACK]           = "Track"
301GpsTagTable[GPS_TAG_IMGDIRECTIONREF] = "ImgDirectionRef"
302GpsTagTable[GPS_TAG_IMGDIRECTION]    = "ImgDirection"
303GpsTagTable[GPS_TAG_MAPDATUM]        = "MapDatum"
304GpsTagTable[GPS_TAG_DESTLATITUDEREF] = "DestLatitudeRef"
305GpsTagTable[GPS_TAG_DESTLATITUDE]    = "DestLatitude"
306GpsTagTable[GPS_TAG_DESTLONGITUDEREF]= "DestLongitudeRef"
307GpsTagTable[GPS_TAG_DESTLONGITUDE]   = "DestLongitude"
308GpsTagTable[GPS_TAG_DESTBEARINGREF]  = "DestBearingref"
309GpsTagTable[GPS_TAG_DESTBEARING]     = "DestBearing"
310GpsTagTable[GPS_TAG_DESTDISTANCEREF] = "DestDistanceRef"
311GpsTagTable[GPS_TAG_DESTDISTANCE]    = "DestDistance"
312GpsTagTable[GPS_TAG_PROCESSINGMETHOD]= "ProcessingMethod"
313GpsTagTable[GPS_TAG_AREAINFORMATION] = "AreaInformation"
314GpsTagTable[GPS_TAG_DATESTAMP]       = "Datestamp"
315GpsTagTable[GPS_TAG_DIFFERENTIAL]    = "Differential"
316
317FMT_BYTE      =  1
318FMT_STRING    =  2
319FMT_USHORT    =  3
320FMT_ULONG     =  4
321FMT_URATIONAL =  5
322FMT_SBYTE     =  6
323FMT_UNDEFINED =  7
324FMT_SSHORT    =  8
325FMT_SLONG     =  9
326FMT_SRATIONAL = 10
327FMT_SINGLE    = 11
328FMT_DOUBLE    = 12
329
330bytes_per_format = {0,1,1,2,4,8,1,1,2,4,8,4,8}
331
332portrule = shortport.http
333
334---Unpack a rational number from exif. In exif, a rational number is stored
335--as a pair of integers - the numerator and the denominator.
336--
337--@return the new position, and the value.
338local function unpack_rational(endian, data, pos)
339  local v1, v2
340  v1, v2, pos = string.unpack(endian .. "I4I4", data, pos)
341  return pos, v1 / v2
342end
343
344local function process_gps(data, pos, endian, result)
345  local value, num_entries
346  local latitude, latitude_ref, longitude, longitude_ref
347
348  -- The first entry in the gps section is a 16-bit size
349  num_entries, pos = string.unpack(endian .. "I2", data, pos)
350
351  -- Loop through the entries to find the fun stuff
352  for i=1, num_entries do
353    local tag, format, components, value
354    tag, format, components, value, pos = string.unpack(endian .. "I2 I2 I4 I4", data, pos)
355
356    if(tag == GPS_TAG_LATITUDE or tag == GPS_TAG_LONGITUDE) then
357      local dummy, gps, h, m, s
358      dummy, h = unpack_rational(endian, data, value + 8)
359      dummy, m = unpack_rational(endian, data, dummy)
360      dummy, s = unpack_rational(endian, data, dummy)
361
362      gps = h + (m / 60) + (s / 60 / 60)
363
364      if(tag == GPS_TAG_LATITUDE) then
365        latitude = gps
366      else
367        longitude = gps
368      end
369    elseif(tag == GPS_TAG_LATITUDEREF) then
370      -- Get the first byte in the latitude reference as a character
371      latitude_ref = string.char(value >> 24)
372    elseif(tag == GPS_TAG_LONGITUDEREF) then
373      -- Get the first byte in the longitude reference as a character
374      longitude_ref = string.char(value >> 24)
375    end
376  end
377
378  if(latitude and longitude) then
379    -- Normalize the N/S/E/W to positive and negative
380    if(latitude_ref == 'S') then
381      latitude = -latitude
382    end
383    if(longitude_ref == 'W') then
384      longitude = -longitude
385    end
386
387    table.insert(result, string.format("GPS: %f,%f - https://maps.google.com/maps?q=%s,%s", latitude, longitude, latitude, longitude))
388  end
389
390  return true, result
391end
392
393---Parse the exif data section and return a table. This has only been tested
394--in a .jpeg file, but should work for .tiff as well.
395local function parse_exif(exif_data)
396  local sig, marker, size
397  local tag, format, components, byte_count, value, offset, dummy, data
398  local status, result
399  local tiff_header_1, first_offset
400
401  -- Initialize the result table
402  result = {}
403
404  -- Read the verify the EXIF header
405  local header, endian, pos = string.unpack(">c6 I2", exif_data, 1)
406  if(header ~= "Exif\0\0") then
407    return false, "Invalid EXIF header"
408  end
409
410  -- Check the endianness - it should only ever be big endian, but it doesn't
411  -- hurt to check
412  if(endian == 0x4d4d) then
413    endian = ">"
414  elseif(endian == 0x4949) then
415    endian = "<"
416  else
417    return false, "Unrecognized endianness entry"
418  end
419
420  -- Read the first tiff header and the offset to the first data entry (should be 8)
421  tiff_header_1, first_offset, pos = string.unpack(endian .. "I2 I4", exif_data, pos)
422  if(tiff_header_1 ~= 0x002A or first_offset ~= 0x00000008) then
423    return false, "Invalid tiff header"
424  end
425
426  -- Skip over the header, and go to the first offset (subtracting 1 because lua)
427  pos = first_offset + 8 - 1
428
429  -- The first 16-bit value is the number of entries
430  local num_entries, pos = string.unpack(endian .. "I2", exif_data, pos)
431
432  -- Loop through the entries
433  for i=1,num_entries do
434    -- Read the entry's header
435    tag, format, components, value, pos = string.unpack(endian .. "I2 I2 I4 I4", exif_data, pos)
436
437    -- Look at the tags we care about
438    if(tag == TAG_GPSINFO) then
439      -- If it's a GPSINFO tag, we need to parse the GPS structure
440      status, result = process_gps(exif_data, value + 8 - 1, endian, result)
441      if(not(status)) then
442        return false, result
443      end
444    else
445      value = string.unpack("z", exif_data, value + 8 - 1)
446      if (tag == TAG_MAKE) then
447        table.insert(result, string.format("Make: %s", value))
448      elseif(tag == TAG_MODEL) then
449        table.insert(result, string.format("Model: %s", value))
450      elseif(tag == TAG_DATETIME) then
451        table.insert(result, string.format("Date: %s", value))
452      end
453    end
454  end
455
456  return true, result
457end
458
459---Parse a jpeg and find the EXIF data section
460local function parse_jpeg(s)
461  local pos, sig, marker, size, exif_data
462
463  -- Parse the jpeg header, make sure it's valid (we expect 0xFFD8)
464  sig, pos = string.unpack(">I2", s, pos)
465  if(sig ~= 0xFFD8) then
466    return false, "Unexpected signature"
467  end
468
469  -- Parse the sections to find the exif marker (0xffe1)
470  while(true) do
471    marker, size, pos = string.unpack(">I2I2", s, pos)
472
473    -- Check if we found the exif metadata section, break if we did
474    if(marker == 0xffe1) then
475      break
476    -- If the marker is nil, we're off the end of the image (and therefore, it wasn't found)
477    elseif(not(marker)) then
478      return false, "Could not found EXIF marker"
479    end
480
481    -- Go to the next section (we subtract 2 because of the 2-byte marker we read)
482    pos = pos + size - 2
483  end
484
485  exif_data, pos = string.unpack(string.format(">c%d", size), s, pos)
486
487  return parse_exif(exif_data)
488end
489
490
491function action(host, port)
492  local pattern = "%.jpg"
493  local images = {}
494  local results = {}
495
496  -- once we know the pattern we'll be searching for, we can set up the function
497  local whitelist = function(url)
498    return string.match(url.file, "%.jpg") or string.match(url.file, "%.jpeg")
499  end
500
501  local crawler = httpspider.Crawler:new(  host, port, nil, { scriptname = SCRIPT_NAME, whitelist = { whitelist }} )
502
503  if ( not(crawler) ) then
504    return
505  end
506
507  while(true) do
508    -- Begin the crawler
509    local status, r = crawler:crawl()
510
511    -- Make sure there's no error
512    if ( not(status) ) then
513      if ( r.err ) then
514        return stdnse.format_output(false, r.reason)
515      else
516        break
517      end
518    end
519
520    -- Check if we got a response, and the response is a .jpg file
521    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
522      local status, result
523      stdnse.debug1("Attempting to read exif data from %s", r.url.raw)
524      status, result = parse_jpeg(r.response.body)
525      if(not(status)) then
526        stdnse.debug1("Couldn't read exif from %s: %s", r.url.raw, result)
527      else
528        -- If there are any exif results, add them to the result
529        if(result and #result > 0) then
530          result['name'] = r.url.raw
531          table.insert(results, result)
532        end
533      end
534    end
535  end
536
537  return stdnse.format_output(true, results)
538end
539
540