#StackBounty: #php #object-oriented #url Parse a clean slug URL

Bounty: 50

I have to implement a “user-friendly” URL for an employment website, the URL /jobs{parameters?} may be decomposed up to three searching criteria separated by -, each criterion is separated by ~. The parameters string has the following structure
-keywords-employmentTypes-locations, keywords and employmentTypes are optional, location is mandatory unless there are no parameters, the route should handle all those cases:

/jobs

/jobs-canada     /jobs-canada~paris~usa

/jobs-full_time-canada  /jobs-full_time~part_time-canada~paris
/jobs-engineer-canada  /jobs-engineer~c++-canada~paris

/jobs-engineer-full_time-canada  /jobs-engineer~c++-full_time-canada~paris

The following class parses the string parameters and fill all criteria in variables:

class JobUrlBuilder
{
    const DEFAULT_LOCATION = 'france';

    public $keywords;
    public $employmentTypes;
    public $locations;

    public $allEmploymentTypes;

    function __construct($allEmploymentTypes = [])
    {
        $this->setAllEmploymentTypes($allEmploymentTypes);
    }

    /**
     * @param string $parameters
    */
    public function setParameters($parameters)
    {
        $parameters = explode('-', ltrim($parameters, '-'));
        if (empty($parameters[0])) {
            array_shift($parameters);
        }

        if (count($parameters) === 3) {
            $this->keywords  = $parameters[0];
            $this->employmentTypes = $parameters[1];
            $this->locations = $parameters[2];
        } else if (count($parameters) === 2) {
            $areEmploymentTypes = $this->areEmploymentTypes(explode('~', $parameters[0]));

            $this->keywords  = $areEmploymentTypes ? null : $parameters[0];
            $this->employmentTypes = $areEmploymentTypes ? $parameters[0] : null;
            $this->locations = $parameters[1];
        } else if (count($parameters) === 1) {
            $this->keywords  = null;
            $this->employmentTypes = null;
            $this->locations = $parameters[0];
        } else {
            $this->keywords  = null;
            $this->employmentTypes = null;
            $this->locations = self::DEFAULT_LOCATION;
        }

        $this->keywords = $this->keywords ? explode('~', $this->keywords) : [];
        $this->employmentTypes = $this->employmentTypes ? explode('~', $this->employmentTypes) : [];
        $this->locations = $this->locations ? explode('~', $this->locations) : [];

        return $this;
    }

    /**
    * @param array $items
    */
    public function areEmploymentTypes($items)
    {
        $jobTypes = array_map(function ($v)
        {
            return mb_strtolower($v);
        }, $this->allEmploymentTypes);

        return ! array_diff($items, $jobTypes);
    }

    /**
    * @param array $employmentTypes
    */
    public function setAllEmploymentTypes($employmentTypes)
    {
        $this->allEmploymentTypes = $employmentTypes;
    }

    public function hasDefaultLocation()
    {
        return in_array(self::DEFAULT_LOCATION, $this->locations);
    }
}

I don’t like this code (spaghetti code, no separation of concerns…), I think there is a better implementation. I’m also open to other URL structures, I know I can simply use input parameters and call it a day, but I should implement a clean URL. I have added unit tests to handle all cases, I think it might help you understand the code.


Get this bounty!!!

#StackBounty: #php #symfony #paypal #payu Symfony – Payum – Adding funds to user's balance

Bounty: 50

I have a Symfony 3.4 project, I wanted to integrate PayumBundle for Paypal Express Checkout, What I’m trying to achieve is the following:
A user can add funds to their account using Paypal Express Checkout, once they fill in the amount they wanted to add, I check if the payment went through and I increase the user’s balance with that amount.
A user can buy “Orders” from the website as well, if they did that, the amount of the order will be deducted from their balance.
and here is what I have set up for now:

Config.yml:

payum:
security:
    token_storage:
        AppBundleEntityPaymentToken: { doctrine: orm }
storages:
    AppBundleEntityPayment: { doctrine: orm }
    AppBundleEntityPaymentDetails: { doctrine: orm }
gateways:
    paypal_express_checkout_default_gateaway:
        factory: paypal_express_checkout
        username:  '**************.email.com'
        password:  '********************'
        signature: '***************'
        sandbox: true

My PaymentDetails class:

    /**
     * @ORMTable(name="payum_payment_details")
     * @ORMEntity
     */
    class PaymentDetails extends ArrayObject
    {
        /**
         * @ORMColumn(name="id", type="integer")
         * @ORMId
         * @ORMGeneratedValue(strategy="IDENTITY")
         */
        protected $id;
        /**
         * @return int
         */


        /**
         * One PaymentDetails has One AccountBalanceTransaction.
         * @ORMOneToOne(targetEntity="AppBundleEntityAccountBalanceTransaction", mappedBy="paymentDetails")
         */
        private $accountBalanceTransaction;

        /**
         * Many PaymentDetails have One User.
         * @ORMManyToOne(targetEntity="ApplicationSonataUserBundleEntityUser", inversedBy="paymentDetails")
         * @ORMJoinColumn(name="user_id", referencedColumnName="id")
         */
        private $user;

    // getters and setter removed

}

AccountBalanceTransaction class:

/**
 * @ORMEntity
 * @ORMTable(name="account_balance_transaction")
 */
class AccountBalanceTransaction
{
    public function __construct()
    {
        $this->setDate(new DateTime());
        $this->paymentDetails = new ArrayCollection();
    }


    /**
     * @ORMId
     * @ORMGeneratedValue
     * @ORMColumn(type="integer")
     */
    private $id;

    /**
     * @ORMColumn(type="decimal", precision=7, scale=2, nullable=true)
     */
    private $credit;

    /**
     * @ORMColumn(type="decimal", precision=7, scale=2, nullable=true)
     */
    private $debit;

    /**
     * @ORMColumn(type="decimal", precision=7, scale=2)
     */
    private $balance;

    /**
     * @ORMColumn(type="datetime")
     */
    private $date;

    /**
     * @ORMColumn(type="text", nullable=true)
     */
    private $description;

    /**
     * One AccountBalanceTransaction Might have One PaymentDetails.
     * @ORMOneToOne(targetEntity="AppBundleEntityPaymentDetails", inversedBy="accountBalanceTransaction")
     * @ORMJoinColumn(name="payment_details_id", referencedColumnName="id", nullable=true)
     */
    private $paymentDetails;


    /**
     * One AccountBalanceTransaction Might have One Order.
     * @ORMOneToOne(targetEntity="AppBundleEntityOrder", inversedBy="accountBalanceTransaction")
     * @ORMJoinColumn(name="order_id", referencedColumnName="id", nullable=true)
     */
    private $order;

    /**
     * Many AccountBalanceTransactions have One User.
     * @ORMManyToOne(targetEntity="ApplicationSonataUserBundleEntityUser", inversedBy="accountBalanceTransactions")
     * @ORMJoinColumn(name="user_id", referencedColumnName="id")
     */
    private $user;
}

And finally, this is my PaymentController class:

class PaymentController extends Controller
{

 /**
     * @ExtraRoute(
     *   "/prepare_add_funds",
     *   name="paypal_express_checkout_prepare_add_funds"
     * )
     *
     * @ExtraTemplate(":payment:prepare.html.twig")
     */
    public function prepareSimplePurchaseAndDoctrineOrmAction(Request $request)
    {
        $user = $this->getUser();
        if (!is_object($user) || !$user instanceof UserInterface) {
            throw new AccessDeniedException('This user does not have access to this section.');
        }

        $gatewayName = 'paypal_express_checkout_default_gateaway';
        $form = $this->createPurchaseForm();
        $form->handleRequest($request);
        if ($form->isValid()) {
            $data = $form->getData();
            $storage = $this->get('payum')->getStorage(PaymentDetails::class);
            /** @var $payment PaymentDetails */
            $payment = $storage->create();
            $amount = $data['amount'];
            $payment['PAYMENTREQUEST_0_CURRENCYCODE'] = 'USD';
            $payment['PAYMENTREQUEST_0_AMT'] = $amount;
            $storage->update($payment);
            $captureToken = $this->get('payum')->getTokenFactory()->createCaptureToken(
                $gatewayName,
                $payment,
                'paypal_express_checkout_done_add_funds'
            );
            $payment['INVNUM'] = $payment->getId();
            $payment->setUser($user);
            $storage->update($payment);

            return $this->redirect($captureToken->getTargetUrl());
        }

        return array(
            'form' => $form->createView(),
            'gatewayName' => $gatewayName
        );
    }

    /**
     * @ExtraRoute(
     *   "/payment/details",
     *   name="paypal_express_checkout_done_add_funds"
     * )
     */

    public function viewAction(Request $request)
    {
        $user = $this->getUser();
        if (!is_object($user) || !$user instanceof UserInterface) {
            throw new AccessDeniedException('This user does not have access to this section.');
        }



        $token = $this->get('payum')->getHttpRequestVerifier()->verify($request);
        $gateway = $this->get('payum')->getGateway($token->getGatewayName());
        try {
            $gateway->execute(new Sync($token));
        } catch (RequestNotSupportedException $e) {}

        $gateway->execute($status = new GetHumanStatus($token));
        $payment = $status->getFirstModel();

        if ($user->getId() != || $payment->getUser()->getId()) {
            throw new AccessDeniedException('This user does not have access to this section.');
        }

        if ($status->isCaptured()) {
            $entityManager = $this->getDoctrine()->getManager();
            $amount = $details['AMT'];

            // check if this paymentDetails already has an AccountBalanceTransaction
            $accountBalanceTransaction = $this->getDoctrine()
                ->getRepository(AccountBalanceTransaction::class)
                ->findOneBy(['paymentDetails' => $payment->getId()]);
            // if not then create a new one and increase the user's balance
            if($accountBalanceTransaction === null){
                $accountBalanceTransaction = new AccountBalanceTransaction();
                $accountBalanceTransaction->setUser($user);
                $accountBalanceTransaction->setPaymentDetails($payment);
                $accountBalanceTransaction->setCredit($amount);
                $accountBalanceTransaction->setDescription('User added funds with Paypal.');
                $accountBalanceTransaction->setBalance($user->getBalance() + $amount);

                $entityManager->persist($accountBalanceTransaction);
                $entityManager->flush();


                $user->setBalance($user->getBalance() + $amount);
                $entityManager->persist($user);
                $entityManager->flush();
            }

            $refundToken = $this->get('payum')->getTokenFactory()->createRefundToken(
                $token->getGatewayName(),
                $status->getFirstModel(),
                $request->getUri()
            );
        }

        return $this->render('payment/view.html.twig', array(
            'status' => $status->getValue(),
            'refundToken' => $refundToken
        ));
    }


    /**
     * @return SymfonyComponentFormFormInterface
     */
    protected function createPurchaseForm()
    {
        return $this->createFormBuilder()
            ->add('amount', null, array(
                'data' => 100,
                'constraints' => array(new Range(array('max' => 10000)))
            ))
            ->getForm()
            ;
    }
}

I’m using FosUserBundle for users management.

This kind of works, but I’m not really happy with it, I think there is a better way to do it, but since I’m new to Payum and payments integration in general, so I’ll need someone else’s help.

Also, I’m not sure if I should be creating the Entity AccountBalanceTransaction in the viewAction, Maybe I should create it in the prepareAction along with PaymentDetails.
But again, what if I did that and the payment failed should I delete it in the view action?


Get this bounty!!!

#StackBounty: #php #ajax #amazon-web-services #amazon-s3 Upload multiple files directly to AWS S3 bucket from browser with PHP

Bounty: 50

I need to make a web form capable of uploading multiple files directly to my AWS S3 bucket from browser with PHP.

I came across this nice solution for single file uploads (https://www.sanwebe.com/2015/09/direct-upload-to-amazon-aws-s3-using-php-html):

<?php
$access_key         = "iam-user-access-key"; //Access Key
$secret_key         = "iam-user-secret-key"; //Secret Key
$my_bucket          = "mybucket"; //bucket name
$region             = "us-east-1"; //bucket region
$success_redirect   = 'http://'. $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']; //URL to which the client is redirected upon success (currently self) 
$allowd_file_size   = "1048579"; //1 MB allowed Size

//dates
$short_date         = gmdate('Ymd'); //short date
$iso_date           = gmdate("YmdTHisZ"); //iso format date
$expiration_date    = gmdate('Y-m-dTG:i:sZ', strtotime('+1 hours')); //policy expiration 1 hour from now

//POST Policy required in order to control what is allowed in the request
//For more info http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
$policy = utf8_encode(json_encode(array(
                    'expiration' => $expiration_date,  
                    'conditions' => array(
                        array('acl' => 'public-read'),  
                        array('bucket' => $my_bucket), 
                        array('success_action_redirect' => $success_redirect),
                        array('starts-with', '$key', ''),
                        array('content-length-range', '1', $allowd_file_size), 
                        array('x-amz-credential' => $access_key.'/'.$short_date.'/'.$region.'/s3/aws4_request'),
                        array('x-amz-algorithm' => 'AWS4-HMAC-SHA256'),
                        array('X-amz-date' => $iso_date)
                        )))); 

//Signature calculation (AWS Signature Version 4)   
//For more info http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html  
$kDate = hash_hmac('sha256', $short_date, 'AWS4' . $secret_key, true);
$kRegion = hash_hmac('sha256', $region, $kDate, true);
$kService = hash_hmac('sha256', "s3", $kRegion, true);
$kSigning = hash_hmac('sha256', "aws4_request", $kService, true);
$signature = hash_hmac('sha256', base64_encode($policy), $kSigning);
?>
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Aws S3 Direct File Uploader</title>
</head>
<body>
<form action="http://<?= $my_bucket ?>.s3.amazonaws.com/" method="post" enctype="multipart/form-data">
<input type="hidden" name="key" value="${filename}" />
<input type="hidden" name="acl" value="public-read" />
<input type="hidden" name="X-Amz-Credential" value="<?= $access_key; ?>/<?= $short_date; ?>/<?= $region; ?>/s3/aws4_request" />
<input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" />
<input type="hidden" name="X-Amz-Date" value="<?=$iso_date ; ?>" />
<input type="hidden" name="Policy" value="<?=base64_encode($policy); ?>" />
<input type="hidden" name="X-Amz-Signature" value="<?=$signature ?>" />
<input type="hidden" name="success_action_redirect" value="<?= $success_redirect ?>" /> 
<input type="file" name="file" />
<input type="submit" value="Upload File" />
</form>
<?php
//After success redirection from AWS S3
if(isset($_GET["key"]))
{
    $filename = $_GET["key"];
    $ext = pathinfo($filename, PATHINFO_EXTENSION);
    if(in_array($ext, array("jpg", "png", "gif", "jpeg"))){
        echo '<hr />Image File Uploaded : <br /><img src="//'.$my_bucket.'.s3.amazonaws.com/'.$_GET["key"].'" style="width:100%;" />';
    }else{
        echo '<hr />File Uploaded : <br /><a href="http://'.$my_bucket.'.s3.amazonaws.com/'.$_GET["key"].'">'.$filename.'</a>';
    }
}
?>
</body>
</html>

It works great for it purpose, but I need a solution that will be able to upload multiple uploads at once.

One of the comments on page specifies an approach:

AWS only allows you to upload one file at a time if uploading directly to S3. You can do multi file uploads by setting the file input to “multiple” and looping through each of the files, making mulitple submissions via AJAX. To do this you need to set up CORS on the bucket you want to upload to, otherwise you’ll be denied on the grounds of it being a cross-site script. It can be accomplished, as I’ve just got it working on my own project.

I am trying to follow, but not sure how exactly he proposes to use an AJAX to make it work. Does the form will be on AJAx request page and I just feed file names to it?

Can someone familiar with an issue can please explain it to me more thoroughly or direct me to the alternative solution(s)?


Get this bounty!!!

#StackBounty: #php #mysql #sql #ajax #recursive-query Order hierarchical query for comments with parent-child relation?

Bounty: 50

I have a comments system that uses the adjacency list to store hierarchical data (i believe), for example: MySql table has the columns id, parent_id, date, … , where a comment without a parent_id is a main comment and a comment with a parent_id is naturally a reply.

On initial page load i make an Ajax call that loads all the comments without a parent_id, so all the main comments.

SELECT * FROM comms WHERE parent_id IS NULL

Now if any of the comments have replies a button like “load replies” appears for that comment and on click another call is made that loads all the comments that have as parent_id that comment id, then the recursive query loads the replies of replies and so on. And that works pretty well the problem is that from the order they are loaded you can’t really tell what is a replay to what.

So what i want is to order them so that a replay is under the comment that it belongs.

Now is this possible only from sql doing something like ORDER BY id = parent_id, ordering them so that they somewhat make sense or should i handle this from php? Or should i just start over and store them in a different way?

Edit: part of the second query (example taken from this answer i found a while back)

SELECT date_p, parent_id, id
   FROM (SELECT * FROM comms) rec,
   (SELECT @pv := 14) initialisation
   WHERE find_in_set(parent_id, @pv) > 0 
   AND @pv := concat(@pv, ',', id) ORDER BY ?

If i would use the “Alternative 1” provided in the answer i liked for, would the method for ordering be different or better?

This is what I am trying to achive:

<p>Main comm 1</p>
  <p>reply to main comm 1</p>
  <p>another reply to main comm 1</p>
    <p> replay to reply of main comm 1</p>
  <p> yet another reply to main comm 1</p>
<p>Main comm 2</p>
<p>Main comm 3</p>


Get this bounty!!!

#StackBounty: #php #facebook #facebook-graph-api #web-crawler Facebook crawler is hitting my server hard and ignoring directives. Acces…

Bounty: 500

The Facebook Crawler is hitting my servers multiple times every second and it seems to be ignoring both the Expires header and the og:ttl property.

In some cases, it is accessing the same og:image resource multiple times over the space of 1-5 minutes. In one example – the crawler accessed the same image 12 times over the course of 3 minutes using 12 different IP addresses.

I only had to log requests for 10 minutes before I caught the following example:

List of times and crawler IP addresses for one image:

2018-03-30 15:12:58 - 66.220.156.145
2018-03-30 15:13:13 - 66.220.152.7
2018-03-30 15:12:59 - 66.220.152.100
2018-03-30 15:12:18 - 66.220.155.248
2018-03-30 15:12:59 - 173.252.124.29
2018-03-30 15:12:15 - 173.252.114.118
2018-03-30 15:12:42 - 173.252.85.205
2018-03-30 15:13:01 - 173.252.84.117
2018-03-30 15:12:40 - 66.220.148.100
2018-03-30 15:13:10 - 66.220.148.169
2018-03-30 15:15:16 - 173.252.99.50
2018-03-30 15:14:50 - 69.171.225.134

What the og:image is according to Facebook’s documentation:

The URL of the image that appears when someone shares the content to
Facebook. See below for more info, and check out our best practices
guide to learn how to specify a high quality preview image.

The images that I use in the og:image have an Expires header set to +7 days in the future. Lately, I changed that to +1 year in the future. Neither setting seems to make any difference. The headers that the crawler seems to be ignoring:

Cache-Control: max-age=604800
Content-Length: 31048
Content-Type: image/jpeg
Date: Fri, 30 Mar 2018 15:56:47 GMT
Expires: Sat, 30 Mar 2019 15:56:47 GMT
Pragma: public
Server: nginx/1.4.6 (Ubuntu)
Transfer-Encoding: chunked
X-Powered-By: PHP/5.5.9-1ubuntu4.23

According to Facebook’s Object Properties documentation, the og:ttl property is:

Seconds until this page should be re-scraped. Use this to rate limit
the Facebook content crawlers. The minimum allowed value is 345600
seconds (4 days); if you set a lower value, the minimum will be used.
If you do not include this tag, the ttl will be computed from the
“Expires” header returned by your web server, otherwise it will
default to 7 days.

I have set this og:ttl property to 2419200, which is 28 days in the future.

I have been tempted to use something like this:

header("HTTP/1.1 304 Not Modified"); 
exit;

But my fear would be that Facebook’s Crawler would ignore the header and mark the image as broken – thereby removing the image preview from the shared story.

A video showing the rate at which these requests from the Crawler are coming in.

Is there a way to prevent the crawler from coming back to hit these resources so soon?

Example code showing what my open graph and meta properties look like:

<meta property="fb:app_id" content="MyAppId" />
<meta property="og:locale" content="en_GB" />
<meta property="og:type" content="website" />
<meta property="og:title" content="My title" />
<meta property="og:description" content="My description" />
<meta property="og:url" content="http://example.com/index.php?id=1234" />
<link rel="canonical" href="http://example.com/index.php?id=1234" />
<meta property="og:site_name" content="My Site Name" />
<meta property="og:image" content="http://fb.example.com/img/image.php?id=123790824792439jikfio09248384790283940829044" />
<meta property="og:image:width" content="940"/>
<meta property="og:image:height" content="491"/>
<meta property="og:ttl" content="2419200" />


Get this bounty!!!

#StackBounty: #php #amazon-web-services #laravel-5 #amazon-sqs #amazon-sns How to use Laravel 5 to receive Amazon SNS message from SQS?

Bounty: 50

I am trying to get the email response (bounce, complaint and delivery) from Amazon SES through SNS. On Amazon SQS console, I see that the message is already in the queue, so I am sure the setting for structures on Amazon is correct.

Then, using Laravel 5.5, following the official guide, I set up a queue listening to SQS. I skip the part of dispatching jobs to the queue as this will be done by SNS. In the job handler, for simplicity, I just var_dump what I receive. The job looks like this:

public function handle($testing_message)
{
    var_dump($testing_message);
    echo "testing handle!n";
}

The config for that looks something like this:

'sqs' => [
    'driver' => 'sqs', //mainly to show that I am using the correct driver
    'key' => env('SQS_KEY', 'your-public-key'),
    'secret' => env('SQS_SECRET', 'your-secret-key'),
    'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
    'queue' => env('SQS_QUEUE', 'your-queue-name'),
    'region' => env('SQS_REGION', 'us-east-1'),
],

For security, The actual value is hidden in .env. I then run:

composer require aws/aws-sdk-php ~3.0
php artisan config:cache
php artisan queue:listen

However, the process just sit there running, no response and no error message.

I want to ask:

  1. How do I know if the connection to the queue is correct?
  2. If the connection is correct, why there is no return from SQS? (I am sure there are already message inside SQS queue from the Amazon console)


Get this bounty!!!

What is #Magento?

Magento is an e-commerce platform built atop open source technology, enabling online sellers to have ultimate control over the look, functionality, and content of their online shops. With Magento, there is the opportunity to deploy powerful marketing strategies, enjoy advanced catalog management, and manage search engine optimisation to maximise your brand’s exposure to your target market.

Today, Magento is one of the best ecommerce platforms available that outstrips other platforms such as WordPress and Shopify when it comes to the control you have over it.

This considerable power comes at a price, though. Magento needs serious hosting setup to shine fully and requires some degree of technical understanding. Granted, even complete novices can design pretty sites with its interface but having knowledge about basic hosting concepts and some scripting cannot hurt.

In addition, there is a large and helpful community of developers and other merchants behind Magento, who are always more than happy to help out newcomers. It is a robust system at its most basic level, and once it is integrated with other systems, its true power is realised.

Magento is super fast too. Any experienced developer will be able to tell you that overall speed is of high importance when it comes to e-commerce. Your customer is not going to wait around for five to 10 minutes for a page to load if she wants to make a purchase. If your site is slow, she will go elsewhere. With Magento, this never has to be an issue as the whole platform is highly optimised and designed to run at lightning fast speed.

If you want to learn more about Magento, check out our infographic below:

#StackBounty: #php #fingerprinting #black-box Guessing PHP version and info from phpinfo using black box analysis

Bounty: 50

Intro

I’m currently experimenting with PHP black box analysis and couldn’t find any useful information. There are some approaches how to determine e.g. Apache version, but for PHP it seems that internet knows only so called “PHP easter eggs”. On php.net I found lots of information about PHP errors, deprecated functions and change logs, but was not able to find anywhere anything similar what I’m searching here for (some sort of comprehensive list or tool or paper or at least ideas). So before reinventing the bicycle I’ll try my luck here.

We have to accept some limitations, which I listed below. For now I’m accepting also error-based tests (since it is difficult to make any guesses without having PHP error messages enabled), but not on every server are PHP error messages enabled.

We don’t have:

  • phpinfo()
  • PHP easter eggs (since PHP>=5.5.0 deprecated and PHP<5.5.0 rewrite rules might be used or expose_php=off (X-Powered-By disabled))
  • no folders, files from known frameworks
  • no framework specific cookies, headers or any other parameters
  • any access to the source code (only exception: public captcha generators or some PayPal/Xsolla/whatever… or other third party scripts)
  • directory listing is off
  • if there are PHP bugs, then exposed path doesn’t tell us about PHP’s version or frameworks, etc
  • so-called “google hacking” doesn’t help us in farming any additional information in this given example

The server is secure – hey man, it belongs to Chuck Norris – so no solutions that rely on exploiting any vulnerabilities, be it 0days, SQL injections, remote code execution or anything else.

We have:

  • the knowledge that PHP is running on the given server
  • PHP bugs (display_errors=on) – wrong input types like: foo.php?id[]=1 instead foo.php?id=1, buggy scripts, host/foo.php/foo.php is allowed causing in some obscure edge cases PHP errors (e.g. file upload), etc.
  • .php extension may be optional, so foo.php?id=baror just /foo/bar/

What I was able to find so far

Guessing PHP version:

- several built-in PHP functions found by analysing PHP error messages
    → PHP change logs → check if any of exposed functions is deprecated in some PHP versions
- PHP<5.3.X allows strings to contain null bytes 
    → problems with include(), copy(), ... (but we don't have such vulnerabilities on that server, e.g. only Alphanumeric input and chars: {.,-_} are allowed, special chars will be replaced with '')
- IF PHP<5.3.0: strlen(Array) = 5
- IF PHP>=7.0.0: casting NaN or infinity to integer = always 0, not more undefined and platform-dependent
- .php3: PHP=3.x.x, .php4: PHP=4.x.x. (trivial)

Guessing infos from phpinfo (without having any access to it):

- foo.php?id=99...99 (large number) → IF response contains:
    → "2 147 483 647" → 32bit system (extra whitespaces for better readability)
    → "9 223 372 036 854 775 807" → 64bit system
- max_post_size VS upload_max_filesize
- number of allowed input parameters:
    → p1[]=1&p2[]=1&... (use fake parameters in some parameter checking loop which doesn't expect wrong input type)
    → e.g. error based detection
- float precision: 2.9999999999999999=3 (16 digits) VS 2.999999999999999=2 (15 digits)
- determine "Timeouts", error based

Question

Is there anything else what can be used in more or less general way to determine server’s PHP version and guess more information which we usually see in phpinfo() (→ php.ini, and other .ini files).

I heard that it is also possible to measure server response times and to correlate them with used PHP functions or PHP versions, but I don’t see any good example for it – PHP scripts might be very complex, so I have no idea how such method should work.

Side note

Let be total overkill and collect all possible known PHP exploits, implement them and just bruteforce the server, by incrementing PHP version’s exploit (if exploit for PHP=5.x.x. doesn’t work, test another exploit for higher PHP version) (I’m speaking here about pure theoretical possibilities and in context of academic research).
Besides all the ethical and legal issues (which we keep in mind here) there are 2 possibilities:

a) Some of the exploits will work (server will be autohacked or DoS-ed by this theoretical exploitation tool, whatever) and we’ll be able to determine the right PHP version (mission accomplished).

b) All exploits will fail, which tells us that server has the newest PHP version or exploits can be applied only for some special cases which we just don’t have here.


Get this bounty!!!

#StackBounty: #php #dom Find text offset into document for DOM attribute

Bounty: 100

How can I find the offset of a particular node or attribute using the PHP DOM extension (or another extension or library if necessary).

For example, say I have this HTML document:

<html><a href="/foo">bar</a></html>

And using the following code (with appropriate modifications):

$dom = new DOMDocument;
$dom->loadHTML($html);
$xpath = new DOMXPath($dom);
$nodes = $xpath->query('//a/@href');
foreach($nodes as $href) {
    // Find start of $href attribute here
    echo $href->something;
}

I’d expect to see the output 15 or something to that effect, to indicate that the attribute starts at character 15 into the document.

There seems to be the method DOMNode::getLineNo() which returns the line number – this is similar to what I want but I can’t find an alternative for the general offset into the text.


Get this bounty!!!

#StackBounty: #php #api #curl curl how to format a curl request with an api key in php

Bounty: 100

hi am new to curl but need it for a particular project could you help me format this code to work i would like to get the results and print out the raw JSON on the page here is the code i am using

$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, "curl -u api key: https://api.companieshouse.gov.uk/search/companies");
$x = curl_exec($curl);
curl_close($curl);
print($x);

this is a link to the api page i am trying to use
https://developer.companieshouse.gov.uk/api/docs/search/companies/companysearch.html

this is the example they give on the page

curl -uYOUR_APIKEY_FOLLOWED_BY_A_COLON: 
https://api.companieshouse.gov.uk/search/companies

these are the parameters for the call if possible i would like to set them as well

q (required)
items_per_page (optional)
start_index (optional)


Get this bounty!!!