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