Hacking dell'API di traduzione di Google

Come parte del suo Google Cloud, Google offre l' API di traduzione di Google con una struttura dei costi basata sull'utilizzo. C'è anche un'API non documentata che può essere utilizzata senza una chiave , ma che si rifiuta di funzionare dopo poche richieste. Quando si utilizza la funzione di traduzione di siti Web di Google Chrome, è evidente che le pagine possono essere tradotte con una qualità molto buona senza alcuna limitazione evidente.


Apparentemente il modello avanzato nmt è già in uso qui. Ma quale API Google Chrome utilizza internamente per tradurre il contenuto e questa API può anche essere indirizzata direttamente, anche sul lato server? Per analizzare il traffico di rete, si consigliano strumenti come Wireshark o Telerik Fiddler , che possono anche analizzare il traffico crittografato. Ma Chrome fornisce anche le richieste che invia per la traduzione della pagina gratuitamente: sono facilmente accessibili tramite Chrome DevTools:

Se esegui una traduzione, prendi la richiesta POST cruciale a https://translate.googleapis.com tramite "Copia> Copia come cURL (bash)" ed eseguila in uno strumento come Postman , ad esempio, puoi inviare di nuovo la richiesta senza problemi:

Anche il significato dei parametri URL è ampiamente ovvio:

ChiaveValore di esempioSenso
anno3Modalità annotazione (influisce sul formato di ritorno)
clientete_libInformazioni sul cliente (varia, il valore è "webapp" tramite l'interfaccia web di Google Translate; ha un effetto sul formato di ritorno e sulla limitazione della velocità)
formatohtmlFormato stringa (importante per tradurre tag HTML)
v1.0Numero di versione di Google Translate
chiaveAIzaSyBOti4mM-6x9WDnZIjIeyEU21OpBXqWBgwChiave API (vedi sotto)
logldvTE_20200210_00Versione protocollo
sldeLinguaggio del codice
tlenLingua di destinazione
spnmtModello ML
tc1sconosciuto
sr1sconosciuto
tk709408.812158Token (vedi sotto)
Moda1sconosciuto

Sono impostate anche alcune intestazioni di richiesta, ma queste possono essere per lo più ignorate. Dopo aver deselezionato manualmente tutte le intestazioni, comprese quelle dall'agente utente , viene rilevato un problema di codifica durante l'immissione di caratteri speciali (qui quando si traduce " Hello World "):

Se riattivi l'agente utente (che generalmente non fa alcun danno), l'API fornisce caratteri codificati UTF-8:

Ci siamo già e abbiamo tutte le informazioni per utilizzare questa API al di fuori di Google Chrome? Se si modifica la stringa da tradurre (campo dati q della richiesta POST), ad esempio, da "Hello world" a "Hello world ! ", Riceviamo un messaggio di errore:

Ora traduciamo di nuovo questo modificato all'interno di Google Chrome utilizzando la funzione di traduzione del sito web e scopriamo che, oltre al parametro q , è cambiato anche il parametro tk (tutti gli altri parametri sono rimasti gli stessi):

Apparentemente è un token che dipende dalla stringa, la cui struttura non è facile da vedere. Quando si avvia la traduzione del sito Web, vengono caricati i seguenti file:

  • 1 file CSS: translateelement.css
  • 4 grafici: translate_24dp.png (2x), gen204 (2x)
  • 2 file JS: main_de.js, element_main.js

I due file JavaScript vengono offuscati e minimizzati. Strumenti come JS Nice e de4js ora ci aiutano a rendere questi file più leggibili. Per eseguire il debug in tempo reale, consigliamo l'estensione di Chrome Requestly, che esegue il tunneling dei file remoti localmente al volo:

Ora possiamo eseguire il debug del codice ( CORS deve essere prima attivato sul server locale). La sezione di codice pertinente per la generazione del token sembra essere nascosta in questa sezione nel file element_main.js:

b7739bf50b2edcf636c43a8f8910def9

Qui il testo è sottoposto ad hashing con l'aiuto di alcuni spostamenti di bit . Ma sfortunatamente ci manca ancora un pezzo del puzzle: oltre all'argomento a (che è il testo da tradurre), un altro argomento b viene passato alla funzione Bp () - una specie di seme che sembra cambiare di volta in volta e che include anche sfocia nell'hashing. Ma da dove viene? Se saltiamo alla chiamata di funzione di Bp () , troviamo la seguente sezione di codice:

b7739bf50b2edcf636c43a8f8910def9

La funzione Hq viene preventivamente dichiarata come segue:

b7739bf50b2edcf636c43a8f8910def9

Qui il Deobfuscater ha lasciato della spazzatura; Dopo aver sostituito String.fromCharCode ('...') con le rispettive stringhe di caratteri, rimuovere l'obsoleto a () e frammentare le chiamate di funzione [c (), c ()] , il risultato è:

b7739bf50b2edcf636c43a8f8910def9

O anche più facile:

b7739bf50b2edcf636c43a8f8910def9

La funzione yq è precedentemente definita come:

b7739bf50b2edcf636c43a8f8910def9

Il seme sembra essere nell'oggetto globale google.translate._const._ctkk , disponibile in fase di esecuzione. Ma dov'è ambientato? Nell'altro file JS precedentemente caricato main_de.js, almeno è disponibile anche all'inizio. Aggiungiamo quanto segue all'inizio:

b7739bf50b2edcf636c43a8f8910def9

Nella console otteniamo effettivamente il seme corrente:

Questo lascia Google Chrome stesso, che apparentemente fornisce il seme, come ultima opzione. Fortunatamente, il suo codice sorgente (Chromium, incluso il componente Translate) è open source e quindi disponibile al pubblico. Estraiamo il repository localmente e troviamo la chiamata alla funzione TranslateScript :: GetTranslateScriptURL nel file translate_script.cc nella cartella components / translate / core / browser:

b7739bf50b2edcf636c43a8f8910def9

La variabile con l'URL è definita in modo rigido nello stesso file:

b7739bf50b2edcf636c43a8f8910def9

Se ora esaminiamo il file element.js più da vicino (dopo aver deoffuscato di nuovo), troviamo la voce hard-set c._ctkk - anche l'oggetto google.translate è impostato di conseguenza e viene attivato il caricamento di tutte le risorse rilevanti (che abbiamo già scoperto in precedenza):

b7739bf50b2edcf636c43a8f8910def9

Ora la chiave del parametro rimane da considerare (con il valore AIzaSyBOti4mM-6x9WDnZIjIeyEU21OpBXqWBgw). Sembra essere una chiave API del browser generica (che può essere trovata anche in alcuni risultati di Google ). È impostato in Chromium nel file translate_url_util.cc nella cartella components / translate / core / browser:

b7739bf50b2edcf636c43a8f8910def9

La chiave viene generata in google_apis / google_api_keys.cc da un valore fittizio:

b7739bf50b2edcf636c43a8f8910def9

Tuttavia, un test mostra che le chiamate API funzionano allo stesso modo senza questo parametro chiave. Se fai esperimenti con l'API, riceverai indietro il codice di stato 200 se hai successo. Se poi incontri un limite, ottieni il codice di stato 411 con il messaggio "Le richieste POST richiedono un'intestazione di lunghezza del contenuto ". È quindi consigliabile includere questa intestazione (che viene automaticamente impostata come intestazione temporanea in Postman).

Il formato di ritorno delle stringhe tradotte è insolito quando ci sono più frasi in una richiesta. Le singole frasi sono racchiuse dai tag i- / b-HTML:

Inoltre, Google Chrome non invia l'intero HTML all'API, ma salva i valori degli attributi come href nella richiesta (e invece imposta gli indici in modo che i tag possano essere assegnati successivamente sul lato client):

Se cambi il valore del client della chiave POST da te_lib (Google Chrome) sulla webapp ( sito di traduzione di Google ), ottieni la stringa tradotta finale:

Il problema è che è molto più probabile che si verifichi un limite di velocità rispetto a te_lib (per confronto: con webapp questo viene raggiunto dopo 40.000 caratteri, con te_lib non c'è limite di velocità). Quindi dobbiamo dare un'occhiata più da vicino a come Chrome analizza il risultato. Lo troveremo qui in element_main.js:

b7739bf50b2edcf636c43a8f8910def9

Se invii l'intero codice HTML all'API, lascia gli attributi nella risposta tradotta. Non dobbiamo quindi imitare l'intero comportamento di analisi, ma solo estrarre la stringa finale tradotta dalla risposta. Per fare ciò, creiamo un piccolo parser di tag HTML che scarta i tag <i> più esterni, compreso il loro contenuto, e rimuove i tag <b> più esterni. Con questa conoscenza (dopo aver installato le dipendenze con il compositore require fzaninotto / faker vielhuber / stringhelper ) possiamo ora creare una versione lato server dell'API di traduzione:

b7739bf50b2edcf636c43a8f8910def9

Di seguito sono riportati i risultati di un test iniziale che è stato effettuato su cinque diversi sistemi con diverse larghezze di banda e indirizzi IP:

CarattereCaratteri per richiestaDurataTasso di erroreCosto tramite API ufficiale
13.064.662~25003: 36: 17h0%237,78€
24.530.510~25011: 09: 13h0%446,46€
49.060.211~25020:39: 10h0%892,90€
99.074.487~100061: 24: 37 ore0%1803,16€
99.072.896~100062: 22: 20h0%1803,13€
Σ284.802.766~ Ø550Σ159: 11: 37h0%Σ € 5183,41

Nota: questo post del blog che include tutti gli script è stato scritto solo a scopo di test. Non utilizzare gli script per uso produttivo, invece lavorare con il funzionario API di Google Traduttore .

Indietro