Blog‎ > ‎

Hunting for hidden parameters within PHP built-in functions (using frida)

posted Feb 2, 2018, 12:13 AM by Emmanuel Law   [ updated Feb 2, 2018, 12:28 AM ]
When I was working on PHP, I had found some "hidden" parameters to PHP built-in functions but had been too busy lazy to document the methodology down. Hopefully this might be of interest to others out there, even though this is pretty simple. It'll also be a good showcase of frida and how easy/powerful it is.

P/S: If you are looking for vulnerabilities, you can stop reading this post now. There is none.

Let's just dive straight into what I mean by "Hidden parameters". The image below shows the API for the built-in function intlcal_from_date_time : 




One can typically call it as follows:

intlcal_from_date_time("1pm")

Notice that the documented API only allows for 1 input. 

Or does it? 

There's actually a 2nd undocumented parameter, that you can pass in to set it's locale. For example you can call it via:

intlcal_from_date_time("1pm", "zh-Hant-TW")

This returns an intlcalender object with its locale set to Chinese, Taiwan.

The question one might ask is, why is there an undocumented parameter? I've no idea. Could it just have been the lack of documentation updates? Maybe. Does it do anything really fanciful ? Not in this case. It just sets the locale. 

However these hidden parameters or discrepancy in the documentation do present an additional attack surface area which is of interest to me for fuzzing.


So the question now is: How does one go about discovering these hidden parameters systematically.

To do that, one must understand how PHP API parses parameters internally within the zend engine. When a call is made to a PHP function, the parameters are being parsed and validated internally by Zend engine's zend_parse_parameters.

In the case of intlcal_from_date_time(), lets' look at the source code within intl/calendar/calendar_methods.cpp:

1
2
3
4
5
6
7
8
U_CFUNC PHP_FUNCTION(intlcal_from_date_time)
{

....


	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z|s!",
			&zv_arg, &locale_str, &locale_str_len) == FAILURE) {

On line 7, You will see that zend_parse_parameters is being called to parse and validate the inputs to intlcal_from_date_time(). It is being validated against the pattern "Z|s!". Here's how to interpreter the pattern:

Z: it is expecting a required zend object ($dateTime in this case)
| : anything after this is optional
s: It takes in an optional string (Which is our hidden parameter Locale in this case)
More information about the patterns can be found here 


So my methodology to discovering these hidden parameters systematically is as follows:
  1. Startup PHP cli binary
  2. Invoke a PHP built-in function
  3. Using frida hook into zend_parse_parameters (and its related sister functions)
  4. Obtain the parameter patterns that is being validated againsts
  5. Compare it against the official documentation.
  6. Rinse repeat for every built-in function within PHP
Before anyone gets too excited, there are only a handful of functions with hidden parameters, not all of them do anything interesting. I'll leave that as an exercise for the reader.

However, this methodology of hooking into zend_parse_parameters does allow me to identify and build a database of exactly what Zend-engine is looking for when calling a PHP built in function. I've found this to be especially useful when I was fuzzing PHP, as the documentation often label a perimeter as "mixed" which can be quite ambiguous.

Let me end off with a simple snipplet of my python + frida code to hook into zend_parse_parameters.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pid = frida.spawn([PHP_BIN, "-r", self.cmdstr])
session = frida.attach(pid)
script = session.create_script("""

    Interceptor.attach(ptr(Module.findExportByName(null, "zend_parse_parameters")), {
        onEnter: function(args) {
          Memory.readCString(args[1]));
          send(Memory.readCString(args[1]));

    }
    });

""")


script.on('message', self.on_message)
script.load()
frida.resume(pid)


def on_message(self,message, data):
  #Parameter Pattern will be stored within message[payload]
  #Validate that against the official PHP documentation



Comments