4

I have a cURL command like this:

curl 'https://www.example.com' \
  -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36' \
  -H 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3' \
  -H 'accept-language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7' \
  -H 'authority: www.example.com'

Executing this in a command line like in Terminal app on my Mac, results to the expected output.

(In case you test it yourself: If this output contains the word Sicherheitsüberprüfung it's geo blocked and you have to use a German IP to test it.)

I transferred the exact command to PHP cURL like this:

<?php
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://www.example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');

$headers = array();
$headers[] = 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36';
$headers[] = 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3';
$headers[] = 'Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7';
$headers[] = 'Authority: www.example.com';
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

$result = curl_exec($ch);
curl_close($ch);
echo $result;
?>

When I run this code I'm getting a message that my request was recognized as automated request/robot: It says Sicherheitsüberprüfung, means security check.

Of course, I'm using the same IP for both, command line and PHP cURL request.

Why that? Isn't command line cURL the same as PHP cURL?

Or is there anything wrong with my PHP script?

UPDATE

I fortuitously found out the following: I'm using Coda as code editor on my Mac. This has a build-in PHP rendering engine. Using this with my PHP script, the result is as expected. It's the same result I'm getting in the command line.

UPDATE 2

I made what Jannes Botis suggested in his answer. I then ran the PHP script in my Coda code editor app (what output the expected) and with MAMP as localhost (what is always recognized as automated request).

I figured out that the the code executed with MAMP was using HTTP/2 while the code executed in Coda is using HTTP/1.1. To solve this, I added the following to the script:

curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);

Now, both output exact the same string:

GET / HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
Authority: www.example.com

But, it's still the same: The one is working, the other is recognized as automated request.

David
  • 2,898
  • 3
  • 21
  • 57
  • PHP script looks fine. Try to capture tcp packets via tcpdump/Wireshark and examine the differences. – Tns Nov 04 '19 at 12:59
  • Have you tried setting the headers using `CURLOPT` constants, e.g. $headers[CURLOPT_USERAGENT] = 'Mozilla/5.0...', etc, and then use `curl_setopt_array()` instead ? – code-kobold Nov 04 '19 at 15:13
  • @Tns I don't really understand what you mean and how to do that with my PHP script and over command line. Do you have a link or something for me? – David Nov 05 '19 at 11:50
  • 1
    Possible duplicate of [php curl: how can i emulate a get request exactly like a web browser?](https://stackoverflow.com/questions/2440729/php-curl-how-can-i-emulate-a-get-request-exactly-like-a-web-browser) – Nico Haase Nov 05 '19 at 12:49
  • @code-kobold There are no constants for `Accept`, `Accept-Language` and `Authority`, are there? – David Nov 05 '19 at 12:49
  • @David `Accept` etc. header date would be an array of strings with `CURLOPT_HTTPHEADER` as key. – code-kobold Nov 05 '19 at 13:05
  • @David, you cannot do much at the application level, you need to go deeper. It takes basic knowledge of TCP/IP and [Wireshark](https://www.wireshark.org/docs/wsug_html_chunked/ChapterIntroduction.html), then you can use its 'follow tcp stream' feature. Threre a lot of YouTube [videos](https://www.youtube.com/results?search_query=wireshark+http) about that. – Tns Nov 05 '19 at 19:31
  • I check the script and it's work fine :) – Dmitry Leiko Nov 06 '19 at 14:10
  • @Dmitry Can you post the output somewhere, e.g. on https://codeshare.io? – David Nov 06 '19 at 14:16
  • @David https://codeshare.io/5Xemr8 – Dmitry Leiko Nov 06 '19 at 15:02
  • @Dmitry Thanks. As you can see, this code contains the word `Sicherheitsüberprüfung` because it's recognized as automated request. What you get when executing the command in command line? – David Nov 06 '19 at 16:29
  • @David https://codeshare.io/5QXdvQ – Dmitry Leiko Nov 06 '19 at 16:38
  • I tested your code and works just fine on my machines. – NVRM Nov 06 '19 at 16:44
  • @Dmitry This is also recognized as automated request. You need to use a German IP, proxy or VPN. – David Nov 06 '19 at 17:42
  • @NVRM Can you please post the output somewhere, e.g. on https://codeshare.io? – David Nov 06 '19 at 17:42
  • Valid answer should _not_ contain "Sicherheitsüberprüfung" word, correct? – Styx Nov 08 '19 at 12:03
  • @Styx Yes, you're right! – David Nov 08 '19 at 13:43
  • Are you uploading and executing the PHP code somewhere other than your machine? – Salman A Nov 12 '19 at 11:26
  • 1
    @SalmanA No, I'm doing it on my Mac. But I tested it on a "real" server. It's the same there. – David Nov 12 '19 at 14:27
  • I too think that this is a possible duplicate of [this](https://stackoverflow.com/questions/15216206/differences-in-using-php-curl-vs-command-line-curl) – champion-runner Nov 13 '19 at 09:48
  • Possible solution is [this](https://incarnate.github.io/curl-to-php/) – champion-runner Nov 13 '19 at 09:48
  • @champion-runner No, doesn't solve my problem. – David Nov 13 '19 at 10:37

3 Answers3

4

Try to debug the request in both cases:

a) Terminal: use curl verbose mode: curl -v and check the http request sent, especially check the header list

b) php curl: print the http request using CURLINFO_HEADER_OUT:

curl_setopt($ch, CURLINFO_HEADER_OUT, true);

curl_exec($ch);

$info = curl_getinfo($ch);
print_r($info['request_header']);

Testing the different headers, what made it work was adding "Pragma: no-cache" header to the request:

$headers[] = 'Pragma: no-cache';

On the other hand, in terminal curl, I had to uppercase the request headers, e.g. User-Agent etc.

Try to create a tcp connection with fsockopen:

$fp = fsockopen("ssl://"."www.example.com", 443, $errno, $errstr, 30);
if (!$fp) {
    echo "$errstr ($errno)<br />\n";
} else {
    $out = "GET / HTTP/1.1\r\n";
    $out .= "Host: www.example.com\r\n";
    $headers = array();
    $headers[] = 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36';
    $headers[] = 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3';
    $headers[] = 'Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7';
    $headers[] = 'Authority: www.example.com';
    $out .= $headers;
    $out .= "Connection: Close\r\n\r\n";
    fwrite($fp, $out);
    while (!feof($fp)) {
        echo fgets($fp, 1024);
    }
    fclose($fp);

and test if this works. Maybe the issue is either that php curl adds some info to the http request or the problem is on the tcp connection level, some info added there.

References

David
  • 2,898
  • 3
  • 21
  • 57
Jannes Botis
  • 11,154
  • 3
  • 21
  • 39
  • I added `$headers[] = 'Pragma: no-cache';` but it's still the same. Can you please post the output somewhere, e.g. on https://codeshare.io? – David Nov 07 '19 at 06:28
  • Hm, I am not sure. Try this: https://stackoverflow.com/questions/27088070/curl-works-from-terminal-but-not-from-php Also you could try to add "Connection: Keep-Alive" header also – Jannes Botis Nov 07 '19 at 10:58
  • `Connection: Keep-Alive` didn't help. BTW: I updated my question with some new details. – David Nov 08 '19 at 13:57
  • @David can you try the tcp request at the 2nd section of the answer? – Jannes Botis Nov 13 '19 at 13:33
3

Command line curl :

It is a tool to transfer data to or from a server, using any of the supported protocols (HTTP, FTP, IMAP, POP3, SCP, SFTP, SMTP, TFTP, TELNET, LDAP or FILE). curl is powered by Libcurl. This tool is preferred for automation, since it is designed to work without user interaction. curl can transfer multiple file at once. For more details for Command line curl

Syntax:

curl [options] [URL...]

Example:

curl http://site.{one, two, three}.com

PHP cURL

$ch = curl_init('http://example.com/wp-login.php');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);

if($this->getRequestType() == 'POST')
{
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, 
        array(
            'user[name]'    => 'Generic+Username',
            'user[email]'   => 'mahekpatel04@gmail.com'
        );
    );
}

$response   = curl_exec($ch);
Mahek Manvar
  • 113
  • 5
0

The issue is with ciphers selected by PHP's cURL by default.

Running curl command with -Ivs options allows us to see what ciphers it uses:

* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH

Setting them in PHP allows it to bypass this mysterious check:

curl_setopt($ch,
  CURLOPT_SSL_CIPHER_LIST,
  'ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH'
);

Also, it seems that Host header and using HTTPv2 should be added:

$headers[] = 'Host: www.11880.com';
// ...
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
Styx
  • 9,863
  • 8
  • 43
  • 53
  • "The issue is with ciphers selected by PHP's cURL by default." That sounds like a very definitive statement, with nothing in the answer to support it. If you determined by testing, you should say so. If it's an educated guess (nothing wrong with that of course) you should also say so. – miken32 Nov 08 '19 at 17:05
  • @Styx I executed my curl command in command line with `-Ivs` and got the following: `* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH` I added `curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH');` to my PHP script but it's still not working. – David Nov 08 '19 at 17:11
  • @miken32 Forgot to point that, sorry. It was determined by testing using the host provided by OP and VPN in Germany. – Styx Nov 08 '19 at 18:28
  • @David Once I added this to my script it started working. Server started respond with `200` rather than `403` and no 'Sicherheitsüberprüfung' anymore. – Styx Nov 08 '19 at 18:33
  • @Styx Did you use the exact code I posted in my question above and only added `curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH');`? Can you please post the output somewhere, e.g. on https://codeshare.io? – David Nov 09 '19 at 06:38
  • @David Yes, I used your code, just added `Host` header, and switched to using HTTP2. Added ciphers too. Here is the code: https://codeshare.io/24vdL4. Here is the full answer: https://codeshare.io/5D4wqm. VPN server used: Germany/Falkenstein. I've updated my answer to reflect changes added. – Styx Nov 13 '19 at 11:30
  • @Styx Yes, your answer is the right one. I took your entire code from https://codeshare.io/24vdL4 but it's still the wrong answer. – David Nov 13 '19 at 15:25
  • @David Sometimes the server redirects to DDoS protection service. Are you sure you're getting answer from the server and not this service? – Styx Nov 13 '19 at 16:26