Skip to content

Instantly share code, notes, and snippets.

@divinity76
Last active August 3, 2020 17:02
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save divinity76/544d7cadd3e88e057ea3504cb8b3bf7e to your computer and use it in GitHub Desktop.
Save divinity76/544d7cadd3e88e057ea3504cb8b3bf7e to your computer and use it in GitHub Desktop.
login to gmail.com with curl, pretending to be a browser.
<?php
declare(strict_types = 1);
// header ( "content-type: text/plain;charset=utf8" );
// https://github.com/divinity76/hhb_.inc.php/blob/master/hhb_.inc.php
require_once ('hhb_.inc.php');
function loginGmail(string $username, string $password, string $recoveryEmailChallengeAnswer) : \hhb_curl
{
$hc = new hhb_curl('', true);
$hc->setopt_array(array(
CURLOPT_TIMEOUT => 20, // i just have a shitty connection :(
CURLOPT_CONNECTTIMEOUT => 10
));
if (0) {
$hc->setopt_array(array(
CURLOPT_USERAGENT => 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1'
));
}
$html = $hc->exec('https://accounts.google.com/signin')->getStdOut();
//hhb_var_dump ( $hc->getStdErr (), $hc->getStdOut (), $inputs ) & die();
$domd = @DOMDocument::loadHTML($html);
$inputs = getDOMDocumentFormInputs($domd, true, false)['gaia_loginform'];
//hhb_var_dump ( $hc->getStdErr (), $hc->getStdOut (), $inputs ) & die();
$loginUrl = $domd->getElementById("gaia_loginform")->getAttribute("action");
$inputs['Email'] = $username;
$html = $hc->setopt_array(array(
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => http_build_query($inputs),
CURLOPT_URL => $loginUrl
))->exec()->getStdOut();
//hhb_var_dump ( $hc->getStdErr (), $hc->getStdOut (), $inputs ) & die();
// without this next GET, login will block you for "not running javascript".. luckily v seems to only be a random number.
$hc->setopt_array(array(
CURLOPT_HTTPGET => 1,
CURLOPT_URL => 'https://accounts.youtube.com/accounts/CheckConnection?pmpo=https%3A%2F%2Faccounts.google.com&' . http_build_query(
array(
'v' => rand(),// ???
'timestamp' => ((int)(microtime(true) * 1000)),
)
),
))->exec();
//hhb_var_dump ( $hc->getStdErr (), $hc->getStdOut (), $inputs ) & die();
$domd = @DOMDocument::loadHTML($html);
$inputs = getDOMDocumentFormInputs($domd, true, false)['gaia_loginform'];
// hhb_var_dump ( $hc->getStdErr (), $hc->getStdOut (), $inputs );
$loginUrl = $domd->getElementById("gaia_loginform")->getAttribute("action");
$inputs['Passwd'] = $password;
{
// a couple of missing ones added by javascript...
$inputs['continue'] = 'https://accounts.google.com/ManageAccount';
$inputs['checkedDomains'] = 'youtube';
$inputs['checkConnection'] = 'youtube:356:1';
$inputs['rip'] = 1;
$inputs['pstMsg'] = 1;
$inputs['rmShown'] = 1;
$inputs['bgresponse'] = 'my_massive_dong';//bgresponse MUST exist, MUST have content, and the content CAN NOT include the phrase "js_disabled"
}
try {
$starttime = microtime(true);
$html = $hc->setopt_array(array(
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => http_build_query($inputs),
CURLOPT_URL => $loginUrl
))->exec()->getStdOut();
//hhb_var_dump($hc->getStdErr(), $hc->getStdOut(), $inputs) & die();
}
finally {
// hhb_var_dump ( $hc->getStdErr (), $hc->getStdOut (), $inputs, (microtime ( true ) - $starttime) ) & die ();
}
$loginErrorText = '';
$domd = @DOMDocument::loadHTML($html);
$xp = new DOMXPath($domd);
$refresh = $xp->query("//meta[@http-equiv='refresh']");
if (!$refresh->length) {
hhb_var_dump($hc->getStdErr(), $hc->getStdOut(), $inputs);;
throw new \RuntimeException("login failed - google did not try to redirect us after sending password!");
}
$refresh = $refresh->item(0);
$content = $refresh->getAttribute("content");
if (false === stripos($content, 'accounts.google.com/ManageAccount')) {
throw new \RuntimeException("login failed - google did not try to redirect us to account management page after sending password!");
}
$nextUrl;
if (!preg_match('/(https\:\/\/[^\;]+)/', $content, $nextUrl)) {
throw new \LogicException("wtf??? should never happen");
}
//hhb_var_dump($nextUrl) & die();
$nextUrl = $nextUrl[1];
$html = $hc->setopt_array(array(
CURLOPT_URL => $nextUrl,
CURLOPT_HTTPGET => 1,
))->exec()->getStdOut();
// TODO: some way to verify that we're actually logged in now.
//hhb_var_dump($hc->getStdErr(), $hc->getStdOut(), $inputs, (microtime(true) - $starttime)) & die();
$html = $hc->setopt_array(array(
CURLOPT_URL => 'https://gmail.com',
CURLOPT_HTTPGET => 1,
))->exec()->getStdOut();
//hhb_var_dump($hc->getStdErr(), $hc->getStdOut(), $inputs, (microtime(true) - $starttime)) & die();
$domd = @DOMDocument::loadHTML($html);
$xp = new DOMXPath($domd);
$authChallenge = $domd->getElementById("challengePickerList");
if (null !== $authChallenge) {
// gotdammit... got an auth challenge page, asking you to choose a challenge
$loginErrorText .= ' - got an auth challenge page, asking you to choose a challenge: ' . trim($authChallenge->textContent);
// TODO: automatically select "provide recovery email" here instead of throwing a login error exception.
} else {
if (false !== stripos($html, 'Enter recovery email') && null !== ($authChallenge = $domd->getElementById("challenge"))) {
// got an auth challenge for providing recovery email..
// luckily, we can automatically recover from this.
$inputs = getDOMDocumentFormInputs($domd, true, false)['challenge'];
assert(array_key_exists('email', $inputs));
$inputs['email'] = $recoveryEmailChallengeAnswer;
$url = $authChallenge->getAttribute("action");
if (!parse_url($url, PHP_URL_HOST)) {
$url = 'https://' . parse_url($hc->getinfo(CURLINFO_EFFECTIVE_URL), PHP_URL_HOST) . $url;
}
$html = $hc->setopt_array(array(
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => http_build_query($inputs),
CURLOPT_URL => $url
))->exec()->getStdOut();
$domd = @DOMDocument::loadHTML($html);
$xp = new DOMXPath($domd);
// TODO: detect incorrect recovery email supplied error here.
}
}
unset($authChallenge);
if (!empty($loginErrorText)) {
throw new \RuntimeException('errors loggin in: ' . $loginErrorText);
} else {
// logged in! :D
}
// now we need to enable HTML view, it's a <form> POST request, but we can't use getDOMDocumentFormInputs (bug?)
if (0) {
foreach ($xp->query("//form") as $form) {
hhb_var_dump($domd->saveHTML($form));
}
die();
}
$res = $xp->query('//form[@id="null" and contains(@action,"zy=d")]');
if ($res->length !== 1) {
$str = 'failed to find HTML version request form!';
hhb_var_dump($str, $hc->getStdErr(), $hc->getStdOut(), $inputs); // & die ();
throw new \RuntimeException($str);
}
$form = $res->item(0);
$url = $form->getAttribute("action");
if (!parse_url($url, PHP_URL_HOST)) {
$url = $hc->getinfo(CURLINFO_EFFECTIVE_URL) . $url;
}
// hhb_var_dump ( $url ) & die ();
$inputs = [];
foreach ($form->getElementsByTagName("input") as $input) {
$name = $input->getAttribute("name");
if (empty($name)) {
continue;
}
$inputs[$name] = $input->getAttribute("value");
}
//hhb_var_dump($inputs) & die();
$html = $hc->setopt_array(array(
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => http_build_query($inputs),
CURLOPT_URL => $url
))->exec()->getStdOut();
//hhb_var_dump($hc->getStdErr(), $hc->getStdOut(), $inputs); // & die ();
return $hc;
}
function rightTrim($str, $needle, $caseSensitive = true) {
$strPosFunction = $caseSensitive ? "strpos" : "stripos";
if ($strPosFunction ( $str, $needle, strlen ( $str ) - strlen ( $needle ) ) !== false) {
$str = substr ( $str, 0, - strlen ( $needle ) );
}
return $str;
}
function getDOMDocumentFormInputs(\DOMDocument $domd, bool $getOnlyFirstMatches = false, bool $getElements = true): array {
// :DOMNodeList?
if (! $getOnlyFirstMatches && ! $getElements) {
throw new \InvalidArgumentException ( '!$getElements is currently only implemented for $getOnlyFirstMatches (cus im lazy and nobody has written the code yet)' );
}
$forms = $domd->getElementsByTagName ( 'form' );
$parsedForms = array ();
$isDescendantOf = function (\DOMNode $decendant, \DOMNode $ele): bool {
$parent = $decendant;
while ( NULL !== ($parent = $parent->parentNode) ) {
if ($parent === $ele) {
return true;
}
}
return false;
};
// i can't use array_merge on DOMNodeLists :(
$merged = function () use (&$domd): array {
$ret = array ();
foreach ( $domd->getElementsByTagName ( "input" ) as $input ) {
$ret [] = $input;
}
foreach ( $domd->getElementsByTagName ( "textarea" ) as $textarea ) {
$ret [] = $textarea;
}
foreach ( $domd->getElementsByTagName ( "button" ) as $button ) {
$ret [] = $button;
}
return $ret;
};
$merged = $merged ();
foreach ( $forms as $form ) {
$inputs = function () use (&$domd, &$form, &$isDescendantOf, &$merged): array {
$ret = array ();
foreach ( $merged as $input ) {
// hhb_var_dump ( $input->getAttribute ( "name" ), $input->getAttribute ( "id" ) );
if ($input->hasAttribute ( "disabled" )) {
// ignore disabled elements?
continue;
}
$name = $input->getAttribute ( "name" );
if ($name === '') {
// echo "inputs with no name are ignored when submitted by mainstream browsers (presumably because of specs)... follow suite?", PHP_EOL;
continue;
}
if (! $isDescendantOf ( $input, $form ) && $form->getAttribute ( "id" ) !== '' && $input->getAttribute ( "form" ) !== $form->getAttribute ( "id" )) {
// echo "this input does not belong to this form.", PHP_EOL;
continue;
}
if (! array_key_exists ( $name, $ret )) {
$ret [$name] = array (
$input
);
} else {
$ret [$name] [] = $input;
}
}
return $ret;
};
$inputs = $inputs (); // sorry about that, Eclipse gets unstable on IIFE syntax.
$hasName = true;
$name = $form->getAttribute ( "id" );
if ($name === '') {
$name = $form->getAttribute ( "name" );
if ($name === '') {
$hasName = false;
}
}
if (! $hasName) {
$parsedForms [] = array (
$inputs
);
} else {
if (! array_key_exists ( $name, $parsedForms )) {
$parsedForms [$name] = array (
$inputs
);
} else {
$parsedForms [$name] [] = $tmp;
}
}
}
unset ( $form, $tmp, $hasName, $name, $i, $input );
if ($getOnlyFirstMatches) {
foreach ( $parsedForms as $key => $val ) {
$parsedForms [$key] = $val [0];
}
unset ( $key, $val );
foreach ( $parsedForms as $key1 => $val1 ) {
foreach ( $val1 as $key2 => $val2 ) {
$parsedForms [$key1] [$key2] = $val2 [0];
}
}
}
if ($getElements) {
return $parsedForms;
}
$ret = array ();
foreach ( $parsedForms as $formName => $arr ) {
$ret [$formName] = array ();
foreach ( $arr as $ele ) {
$ret [$formName] [$ele->getAttribute ( "name" )] = $ele->getAttribute ( "value" );
}
}
return $ret;
}
@manusia21
Copy link

Please add usage example

@jamenlang
Copy link

$test = loginGmail('xxx@xxx.net', 'sumdumpassword', 'recovery@xxx.com');

var_dump($test->getStdOut());

@eray989
Copy link

eray989 commented Nov 16, 2018

not working bro

@divinity76
Copy link
Author

divinity76 commented Feb 8, 2019

@manusia21 and @jamenlang and @eray989 - google made it harder to log in, was difficult but now the code is updated and working ( 2019-02-08 ) - also @jamenlang's code is correct, but note that it requres hhb_curl from https://github.com/divinity76/hhb_.inc.php/blob/master/hhb_.inc.php

@sutorrinaldi
Copy link

not working sir
how to use?
call email | pass

@sutorrinaldi
Copy link

not working sir
login failed - google did not try to redirect us after sending password!

@Nzt2
Copy link

Nzt2 commented Oct 3, 2019

Not work
You're trying to log into a browser or app where we can't protect your account.

Use a different browser.

@divinity76
Copy link
Author

@sutorrinaldi @Nzt2 Google changed the login system again,
i can probably fix it for $50, but since i don't need it myself now, i won't waste time updating it for free.

@Ap0x
Copy link

Ap0x commented Aug 3, 2020

@sutorrinaldi @Nzt2 Google changed the login system again,
i can probably fix it for $50, but since i don't need it myself now, i won't waste time updating it for free.

can you write me telegram or skype i can pay you if you can fix problem ? cryptr.org@gmail.com

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment