RPC method calls with bad parameter counts can cause PHP notices

PEAR Bug #4231 for package XML_RPC, reported by Mike Naberezny on 4 May 2005. See also my addendum to this report.

Description

If a client calls an RPC method but supplies a parameter count that does not match any of the method's signatures as defined in the dispatch map, the function verifySignature() of XML_RPC_Server will cause PHP undefined variable notices if PHP notices have not been disabled.

This is a security issue because it exposes the script path to the client. Also, having the server's XML response prepended with PHP notices will cause many clients to reject the return payload as invalid during parsing.

Reproduce Code

To demonstrate this bug, we can use slightly modified versions of the server and client examples from the end-user documentation. This bug exists in the class XML_RPC_Server, and the class XML_RPC_Client can be used to demonstrate it.

First, create a create a simple XML-RPC server script with XML_RPC_Server and place it on the test webserver:

<?php
require_once('XML/RPC/Server.php');

$server = new XML_RPC_Server(array(), 1, 1);
?>

We will use XML_RPC_Server's built-in system.methodSignature() RPC method to demonstrate the bug, so no dispatch map or test functions need to be created.

The bug in XML_RPC_Server occurs when an RPC method is called with a bad parameter count, i.e. too few or too many parameters than are defined by the signature(s) of the method in the server's dispatch map. The method system.methodSignature() takes only one parameter, an XML-RPC string.

To demonstrate the bug, we will use a modified version of the end-user documentation's client script, and we'll incorrectly call system.methodSignature() with an extra parameter (two instead of one). In practice, it's quite easy to call a method with incorrect or extra parameters, especially when working with a new API or typing quickly on languages with an interactive mode.

<?php
require_once('XML/RPC.php');

$p = new XML_RPC_Value('system.methodSignature', 'string');
$params = array($p, $p);

/**
  * Call system.methodSignature() with a bad parameter count.  This method takes only one
  * parameter, and supplying an extra bogus parameter will demonstrate the bug in the server.
  */
$msg = new XML_RPC_Message('system.methodSignature', $params);

$cli = new XML_RPC_Client('/demo-server.php', 'localhost');
$cli->setDebug(1);
$resp = $cli->send($msg);

if (!$resp) {
    die('Communication error: ' . $cli->errstr);
}

if ($resp->faultCode()) {
    echo('Fault Code: ' . $resp->faultCode() . "\n");
    echo('Fault Reason: ' . $resp->faultString() . "\n");
}
?>

Expected Result

We would expect one of two behaviors: either the method would succeed and ignore the extra bogus parameter, or it would fail with a faultCode and faultString. The latter behavior is preferred, since the client should be alerted that it is calling the method incorrectly.

The client should have shown a faultCode 3 returned by the server:

Content-type: text/html
X-Powered-By: PHP/5.0.3

<PRE>---GOT---
HTTP/1.1 200 OK
Date: Thu, 05 May 2005 03:17:30 GMT
Server: Apache (Unix)
Content-Length: 565
Connection: close
Content-Type: text/xml; charset=UTF-8

<?xml version="1.0" encoding="UTF-8"?>
<!-- PEAR XML_RPC SERVER DEBUG INFO:

0 - new XML_RPC_Value("system.methodSignature", 'string') 
1 - new XML_RPC_Value("system.methodSignature", 'string') 

-->
<methodResponse>
<fault>
  <value>
    <struct>
      <member>
        <name>faultCode</name>
        <value><int>3</int></value>
      </member>
      <member>
        <name>faultString</name>
        <value><string>Incorrect parameters passed to method: Bad parameter count</string></value>
      </member>
    </struct>
  </value>
</fault>
</methodResponse>

---END---
</PRE>Fault Code: 3
Fault Reason: Incorrect parameters passed to method: Bad parameter count

Actual Result

The actual result of running the example code above is that faultCode 3 is indeed produced, however the verifySignature() function in XML_RPC_Server can also cause PHP undefined variable notices if they have not been turned off. The line number shown in the notices will vary depending on the version of the XML_RPC package used, however the bug is present in all versions up to and including 1.3.0RC1.

Content-type: text/html
X-Powered-By: PHP/5.0.3

<PRE>---GOT---
HTTP/1.1 200 OK
Date: Thu, 05 May 2005 03:43:08 GMT
Server: Apache (Unix)
Content-Length: 963
Connection: close
Content-Type: text/xml; charset=UTF-8

<br />
<b>Notice</b>:  Undefined variable: wanted in <b>/your/path/here/PEAR/XML/RPC/Server.php</b> on line <b>354</b><br />
<br />
<b>Notice</b>:  Undefined variable: got in <b>/your/path/here/PEAR/XML/RPC/Server.php</b> on line <b>354</b><br />
<br />
<b>Notice</b>:  Undefined variable: pno in <b>/your/path/here/XML/RPC/Server.php</b> on line <b>354</b><br />
<?xml version="1.0" encoding="UTF-8"?>
<!-- PEAR XML_RPC SERVER DEBUG INFO:

0 - new XML_RPC_Value("system.methodSignature", 'string') 
1 - new XML_RPC_Value("system.methodSignature", 'string') 

-->
<methodResponse>
<fault>
  <value>
    <struct>
      <member>
        <name>faultCode</name>
        <value><int>3</int></value>
      </member>
      <member>
        <name>faultString</name>
        <value><string>Incorrect parameters passed to method: Wanted , got  at param )</string></value>
      </member>
    </struct>
  </value>
</fault>
</methodResponse>


---END---
</PRE>Fault Code: 2
Fault Reason: Invalid return payload: enable debugging to examine incoming payload

The return payload from the server shows that faultCode 3 was produced, however the XML was prepended by the PHP notices. Most XML-RPC clients will reject the payload as invalid when they encounter the notices during XML parsing. This is true of XML_RPC's client XML_RPC_Client, which reports its own faultCode 2 as shown above.

Fixing the Bug

The bug can be found in the function verifySignature() of XML_RPC_Server. The function could be rewritten to detect these cases, however the least invasive way to fix the bug is to replace the last line of verifySignature():

return array(0, "Wanted ${wanted}, got ${got} at param ${pno})");

with this simple check:

if (isset($pno)) {
    return array(0, "Wanted ${wanted}, got ${got} at param ${pno})");
} else {
    return array(0, 'Bad parameter count');
}

 


Last page update: 4 May 2005.