Showing posts with label php. Show all posts
Showing posts with label php. Show all posts

Tuesday, December 13, 2016

How to Authenticate WHMCS Admin Users with PHP

Over the past few days I've been working on a project that involved building an authentication mechanism for a new website which checks user logins against a WHMCS admin database. There are a variety of options for authenticating normal, non-admin WHMCS users: on the easy side of things, you can simply use the WHMCS API's validatelogin() call, or for a more advanced project its possible to implement OAuth within your WHMCS instance. For my project, neither LDAP nor Active Directory were options.

I was surprised to find that the WHMCS API did not contain a mechanism for authenticating admin users. I'm somewhat sympathetic given the security implications: WHMCS is a billing application and it should not be used to provide a sortof infrastructure authentication backbone, particularly given the many much more mature options available for this sort of thing. With that said, this project wasn't about looking to turn WHMCS into LDAP ... it was about allowing WHMCS admin to authenticate into a custom application that was directly and inextricably linked to WHMCS functionality.

When I came up empty on the API front I started Googling for a reasonable alternative, and I found a small number of other options. I became interested in the idea of building my own WHMCS API function to take care of this, but I still needed to take care of the authentication mechanism itself. WHMCS has a page in its documentation that describes in general terms how Admin passwords are hashed, and this page even contains PHP code samples that purport to allow you to auth admin user:password combinations. There are two samples; the first sample demonstrates how to use the WHMCS\Auth namespace and the comparePasswords() function, like so:

use WHMCS\Auth;
 
$authAdmin = new Auth;
 
if ($authAdmin->getInfobyUsername($username) && $authAdmin->comparePassword($password)) {
    $isValid = true;
} else {
    $isValid = false;
}

Pretty straightforward; and this sample works as far as it goes. However, WHMCS provides a second, more thorough example demonstrating how to use the function within a form. You can download a ZIP fie containing this sample here. Unfortunately, this second snippet is broken in a number of places. This second example provides a single file that contains an HTML form with some javascript to display a popup notification when an authentication failure occurs, and a PHP script that takes care of the password comparison. It is the PHP that has problems. I found a variety of fatal errors which made the example unusual: the WHMCS\Auth namespace was called in the wrong scope, the include for the WHMCS init Autoloader is called within a function in such a way that it remains unavailable for other functions, the example uses a class - WHMCS_Auth - which does not exist ... it took a little while for me to sort them out.

Anyway, I found the experience irksome enough that I posted a corrected version of the WHMCS Admin authentication script in a Github repo so that no one else will have to deal with this in the future. I've tested my new version in WHMCS 6.3.1; no guarantees for the latest version 7 at this time, but I can guarantee that WHMCS' example won't work in 7.

I hope it helps!

Tuesday, July 19, 2016

Can the WHMCS API retrieve Product Bundle information?

    Recent versions of WHMCS introduce a feature called "Product Bundles". The idea is simple - a single link combines a number of products, possibly with a specific set of Configurable Options. These combinations can then be assigned discounts unique to that Bundle.
    This comes in handy for a variety of different scenarios, for example in WHMCS instances where multiple brands are in play and you want to run a sale on a single brand that is still sold on other sites.     I recently encountered a situation that involved integrating a pre-existing pricing form within a CMS platform (think Joomla/Wordpress/Drupal/etc) with an order form and series of products with WHMCS. Not only did the signup buttons on the CMS page for each product need to connect to the corresponding WHMCS order form page, but pricing and product details on the CMS page needed to be generated dynamically from information stored in WHMCS, because updating two platforms to make simple price changes sucks and invites user error.
    The easiest way to do this would be to setup an individual product within WHMCS for each product on the CMS page, use the product link generated for the product upon creation in WHMCS and the `Get_Products` WHMCS API call to generate the product details.
    Ultimately this approach proved problematic in my case. Each product on the CMS represented a preset of Configurable Options, but customers needed to be able to easily upgrade these options either during a purchase or after the purchase. I also wanted to be able to easily pipe these configurable options as input to third party modules and custom scripts that would be used to automatically deploy the products based on customer selections (think automatically handling routing based on how many IP addresses a customer buys). Plus, all of these options were part of the primary product and wouldn't make sense to bill them independently like WHMCS' Add-On products.
    I quickly encountered a problem. The `Get_Products` WHMCS API call only allows developers to filter results by Product ID number ("pid") and Product Group ID number ("gid"). This was unacceptable - in order for me to return results by Product Bundle, I needed to search by Bundle ID ("bid"). It became apparent that there was no simple existing method within WHMCS to accomplish this. So, I wrote my own.
    Googling around I noticed I am not the only person who has needed to accomplish something similar, so I am posting the results here in the hope that it can help save someone else a few minutes of irritation or even having people needlessly create a bunch of different products.
    The example I have posted here produces results in json, but it can be pretty easily modified to generate xml instead. Its been tested thoroughly in WHMCS v6.3. The data can be imported into Wordpress using this guide or into Drupal using this guide (requires Views 3 or above & Views JSON Query).


Wednesday, July 29, 2015

PHP logging timestamp oddities

I noticed something odd yesterday while reviewing log data on one of the RHEL 7 web servers I look after. Peering through the PHP error log, I noticed that all of the timestamps were formatted using the Coordinated Universal Time (UTC ... because acronyms that make sense are for losers).

[29-Jul-2015 14:26:04 UTC] PHP [redacted] on line 511
[29-Jul-2015 14:26:04 UTC] PHP [redacted] on line 530
[29-Jul-2015 14:26:04 UTC] PHP [redacted] on line 574
[29-Jul-2015 14:26:04 UTC] PHP [redacted] on line 607
[29-Jul-2015 14:26:04 UTC] PHP [redacted] on line 629

There is nothing wrong with UTC. UTC avoids the calamities inherent in the highly politicized, frequently changed, deeply flawed and inevitably pointless Daylight Savings rules. And unlike epoch-based timestamps, UTC is human readable. It's good stuff. Your hwclock should use it.

With that said, with this particular server a decision was made for logging to consistently be Eastern Time. So I jumped through a number of hoops to make this the case while maintaining reliability. I set the system clock timezone, and enabled regular check-ins with an NTP server pool:

# timedatectl
      Local time: Wed 2015-07-29 10:40:22 EDT
  Universal time: Wed 2015-07-29 14:40:22 UTC
        RTC time: Wed 2015-07-29 14:40:21
        Timezone: America/New_York (EDT, -0400)
     NTP enabled: yes
NTP synchronized: yes

PHP itself demands that timezone be explicitly declared in php.ini. Failing to do so produces `E_NOTICE` notifications in PHP logs. So, I assigned that value to Eastern time also:

[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
date.timezone = America/New_York

Meanwhile, this server uses rsyslog v7.4.7, and relies on the default timestamp template for logs handled by rsyslog.

# rsyslogd -v
rsyslogd 7.4.7, compiled with:
        FEATURE_REGEXP:                         Yes
        FEATURE_LARGEFILE:                      No
        GSSAPI Kerberos 5 support:              Yes
        FEATURE_DEBUG (debug build, slow code): No
        32bit Atomic operations supported:      Yes
        64bit Atomic operations supported:      Yes
        Runtime Instrumentation (slow code):    No
        uuid support:                           Yes

# less /etc/rsyslog.conf
[...]
# Use default timestamp format
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat

The upshot of this is that every log file on this server that uses time stamps uses the Eastern Time Zone, even `dmesg -e`, with the sole exception of the PHP error log. I point out the rsyslog settings despite the fact that php.ini's error log settings could give readers the impression that with the current configuration of my system syslog settings wouldn't be managing this situation.

log_errors = On
; Log errors to specified file. PHP's default behavior is to leave this value
; empty.
; http://php.net/error-log
; Example:
;error_log = php_errors.log
; Log errors to syslog (Event Log on NT, not valid in Windows 95).
;error_log = syslog
error_log = /var/log/httpd/php.log

See how the role of syslog is a big vague?

Its entirely possible I have overlooked something painfully obvious. It certainly wouldn't be the first time. I am just a dude; I make mistakes (and yet the Dude abides).

But - I am not the first person to come across this issue. Bug report #45191 was filed over 7 years ago to address this issue:

[2008-06-05 23:50 UTC] info at organicdata dot co dot za
Description:
------------
I've noticed that changing the default PHP timezone using either php.ini date.timezone or date_default_timezone_set appears to have no effect on the timestamp used for each entry PHP writes to the file set by php.ini value error_log (when php.ini log_errors = On)

It seems to use the system timestamp regardless. I've done some searching on the web but found nothing and am afraid I'm not sure whether a bug or by design but it seems strange enough to submit here

Derick Rethans handled the bug report initially. I'm not going to bust Derick's balls too hard; I don't know him personally but even a brief look at his contribution history is enough to demonstrate his competency as a developer. He has certainly done more for the PHP project than I have.

That said, Derick wasn't interested in dealing with this.

[2008-07-14 10:06 UTC] derick@php.net
Thank you for taking the time to write to us, but this is not
a bug. Please double-check the documentation available at
http://www.php.net/manual/ and the instructions on how to report
a bug at http://bugs.php.net/how-to-report.php

This is normal. The error log is not *written* by PHP, but by syslog. Syslog doesn't care about PHP's internal timezone, and thus formats the log message according to the system timezone. Just change the system timezone if it's incorrect.

MAN. You are a dumbass info at organicdata dot co dot za. RTFM, right? organicdata's reply would seem to confirm Derick's finding. It was a lengthy complaint about what a hassle it is to set timezone in two places, particularly in situations where your server in the Netherlands is hosting HawaiiAutoMechanics.Biz or something. This scenario is indeed a bummer, but don't forget what's going on with my server - changing the system timezone doesn't do a damn thing with this issue (in a version of PHP two minor releases later: 5.2 -> 5.4). Fortunately, Jani jumps in with here 2 cents.

[2008-07-28 22:46 UTC] jani@php.net
Actually error_log="somefile.log" does not use any syslog stuff to write the entries in it. This is the line from main.c:490 which gets executed if error_log != syslog:

strftime(error_time_str, sizeof(error_time_str), "%d-%b-%Y %H:%M:%S", php_localtime_r(&error_time, &tmbuf));

There are 2 problems here: [a] it's using locale sensitive %b modifier [b] It doesn't care about date.timezone. 

Solutions:
[a] IMO it should use this pattern instead: "%Y-%m-%d %H:%M:%S" (f.e. lighttpd uses this for it's error_log entries :)
[b] I don't know how to safely achieve the above mentioned issues with date.timezone vs. system timezone. Might be better leave this as is..


To which Derick responds:

[2008-07-29 06:46 UTC] derick@php.net
It should be switched from strftime() to php_format_date(). This is not an issue with the Date/Time functionality though, but with the syslog one.




[2009-05-03 19:09 UTC] derick@php.net
This bug has been fixed in CVS.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.
 
Thank you for the report, and for helping us make PHP better.

All better! 

Well, not quite. strftime() was in fact replaced, and references to `%b` were removed, in the PHP source files.

However by 5.3.8, the bug was being reported continuously for RHEL and CentOS users. As of my version, here is what I believe to be the relevant handler; starting from main.c:615

 /* Try to use the specified logging location. */
 if (PG(error_log) != NULL) {
#ifdef HAVE_SYSLOG_H
  if (!strcmp(PG(error_log), "syslog")) {
   php_syslog(LOG_NOTICE, "%s", log_message);
   PG(in_error_log) = 0;
   return;
  }
#endif
  fd = VCWD_OPEN_MODE(PG(error_log), O_CREAT | O_APPEND | O_WRONLY, 0644);
  if (fd != -1) {
   char *tmp;
   int len;
   char *error_time_str;

   time(&error_time);
#ifdef ZTS
   if (!php_during_module_startup()) {
    error_time_str = php_format_date("d-M-Y H:i:s e", 13, error_time, 1 TSRMLS_CC);
   } else {
    error_time_str = php_format_date("d-M-Y H:i:s e", 13, error_time, 0 TSRMLS_CC);
   }
#else
   error_time_str = php_format_date("d-M-Y H:i:s e", 13, error_time, 1 TSRMLS_CC);
#endif
   len = spprintf(&tmp, 0, "[%s] %s%s", error_time_str, log_message, PHP_EOL);

Additional bug reports were opened where #45191 left off in 2012, demonstrating errors in IIS, Debian, Gentoo and other operating systems. Apparently, the fix for this issue caused segfaults in Windows ZTS builds per bug report #60373. A patch was released in report #60723. The distinction between the patch and the release I am using is ... subtle to say the least.

@@ -627,7 +627,15 @@ PHPAPI void php_log_err(char *log_message TSRMLS_DC)
    char *error_time_str;
 
    time(&error_time);
-   error_time_str = php_format_date("d-M-Y H:i:s e", 13, error_time, 0 TSRMLS_CC);
+#ifdef ZTS
+   if (php_during_module_startup()) {
+    error_time_str = php_format_date("d-M-Y H:i:s e", 13, error_time, 0 TSRMLS_CC);
+   } else {
+    error_time_str = php_format_date("d-M-Y H:i:s e", 13, error_time, 1 TSRMLS_CC);
+   }
+#else
+   error_time_str = php_format_date("d-M-Y H:i:s e", 13, error_time, 1 TSRMLS_CC);
+#endif
    len = spprintf(&tmp, 0, "[%s] %s%s", error_time_str, log_message, PHP_EOL);
 #ifdef PHP_WIN32
    php_flock(fd, 2);

The only difference appears to be the inversion of the `if (php_during_module_startup())` loop, and the attendant flipping of the TSRMLS_CC that would appear to ensure that the patch is substantively identical to the source for 5.4.16.

This sort of thing is just obnoxious enough to drive my OCD side up a wall, while not posing any serious security or functionality risks given the current deployment it's not worth it to spend a ton of energy testing multiple versions of PHP to resolve it.

Has anyone had success resolving this issue with more recent versions of PHP? Let me know!

Sunday, February 1, 2015

Uploading HTML forms to Amazon S3 using PHP

Dynamically uploading information to S3 can be a bit challenging to do initially, particularly in PHP where a lot of the documentation is either really new or really old.

Amazon has a PHP SDK, which is available as either a .phar file or can be installed using Composer. That's cool for building a new project, but what if you have a pre-existing project or form and just want to be able to dump the text output to S3?

I've put together some code at Github that will take care of that issue. The only requirement is PHP and an Amazon S3 account.

Download or clone the files here: https://github.com/jwieder/s3-http-php-form

Your Amazon access keys and other configuration are stored in a single configuration file. Just fill out your login info into the configuration file and include the php form where you need it as outlined in the README.md file and you should be all set!

Wednesday, October 31, 2012

FastCGI and Application Pool CPU Limiting in IIS7

Or, How To Fix the "Unable to place a FastCGI process in a JobObject" / 0x80070005 Error When Applying a CPU Limit to an IIS7 Application Pool

Here is our example - you have a website that uses several different programming languages running on an IIS7 server. Perhaps your main site is running .NET, and you are using PHP for the website's blog, or Python for a mailing script. You have installed the FastCGI module to speed things up and have it configured successfully.

Unfortunately, CPU utilization is overall fairly high for this site. You want to make sure that it doesn't get *too* high and crash the server, or overwhelm other applications and services you have running on the same server. This article assumes that you already have configured a dedicated application pool for your site, and per the best practices you are running the application pool under a unique application pool identity user, and not the Network Service. It also assumes that you only have one application pool configured for the site - handling both 


When you open task manager, quite a bit of the CPU utilization is being used by w3wp.exe processes - configuring FastCGI has the php-cgi.exe processes under control.
You decide to configure CPU limits for the application pool. This can be accomplished by opening IIS Manager, selecting Application pools from the left hand side, selecting your application pool, clicking Advanced Settings and reconfiguring the values under the CPU header. At a minimum, you will need to set Action to KillW3wp, set a limit (the values are assigned in 1/1000th of 1 percent so don't forget to carry your decimal point!) and assign a reset interval to ensure the application pool is reset and not left in an off state during the few hours a week that you as a server administrator are allowed to sleep.
Normally, this would work just fine. But with FastCGI applied to your site, PHP will become unresponsive, and provide the following error:

* Unable to place a FastCGI process in a JobObject. Try disable the Application Pool CPU Limit feature * Error Number: 5 (0x80070005). * Error Description: Access is denied. 


In a nutshell, FastCGI places php-cgi.exe processes inside of job objects. The Windows Process Activation Service does the same thing when CPU limits have been applied. Having both active means that Windows will try to put one job object inside of the other, which is verboten. 

Fortunately, there is a hotfix available (KB970208) that prevents this nesting behavior from occurring. Download it here. After downloading, restart the server and the error should be resolved. 

Another alternative is implementing Windows Server Resource Manager. Arguably WSRM is the preferred solution, however it deserves its own (forthcoming) post, as WSRM capabilities extend way beyond a FastCGI band-aid.

What about Windows Server 2003? Unfortunately, you are out of luck in that scenario in terms of an easy hotfix. For Windows Server 2003 users, it is necessary to segment FastCGI and non-CGI applications into different folders and create distinct application pools for both. Then you can manage CPU limiting features separately without issue.

Tuesday, September 18, 2012

Disable Display_Errors in Production

Its a simple message, but worth repeating.

Yesterday I came across the website of a major internet security firm making a few first-day-on-the-job mistakes. While I am not going to "out" them before contacting them directly, what they did is silly enough that it warrants a bit of discussion in the abstract.

Display_errors was enabled in their web server's php.ini. As a result, a few helpful messages were displayed briefly at the top of several of pages on the site

1. The name of the database
2. The name of the table in use by that page
3. A list of every column in that table
4. An error indicating that the table is exceeding its maximum allowable size of 4GB

The site collects information about its users - IP address, browser info, referrer, etc, and stores that information to a table in a MySQL database - we know from the error itself that database is running on a server using a 32 bit operating system. With the structure of the database, we have everything we need for SQL injection.

That said, there was one additional level of security. The internet security company is masking database queries in URLs - clicking around the site, it became clear that calls to the database are encoded using an encrypted hash. Unfortunately for the internet security company, this technique obscures rather than protects. Because the hashing is not applied consistently across all requests, it actually helps to draw attention to pages that require further scrutiny from an attacker. And of course, since we already have the structure of a database, we can use php's md5 and sha-1 functions to encode our own injection attempts. I will give the internet security company bonus points for not using base64 encoding for the masking - its surprising how often base64 is used for this purpose when it is trivial to decode and under normal circumstances does not provide any performance benefit. If anyone knows of some good reasons to use base64, please shoot me an email or leave a comment.

In summary - I hope this post helps to illustrate how displaying error information, a very common mistake, can lead to the compromise of an otherwise secure site. I left out a lot of information as it was not my intention to create a "how to" guide on SQL injection. That said I hope I found a good balance between providing a practical example without encouraging nasty behavior. Always remember to disable remote error reporting and debugging features on production servers. When debugging is needed, enable it just long enough to resolve the issue and then turn it back off. Do not attempt to use verbosity settings as a way of securely displaying error information - it only takes one poorly worded error message to compromise a site.