Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add secure note token #138

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion canarydrop.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Canarydrop(object):
'imgur_token' ,'imgur', 'auth', 'browser_scanner_enabled', 'web_image_path',\
'web_image_enabled', 'type', 'clonedsite', 'aws_secret_access_key',\
'aws_access_key_id', 'redirect_url', 'region', 'output', 'slack_api_key',\
'wg_key', 'kubeconfig']
'wg_key', 'kubeconfig', 'secure_note_text', 'secure_note_ciphertext', 'secure_note_initialization_vector']

def __init__(self, generate=False, **kwargs):
self._drop = {}
Expand Down
14 changes: 13 additions & 1 deletion channel_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from queries import get_canarydrop, add_canarydrop_hit, add_additional_info_to_hit
from constants import INPUT_CHANNEL_HTTP
from settings import TOKEN_RETURN, MAX_UPLOAD_SIZE, WEB_IMAGE_UPLOAD_PATH
import settings

env = Environment(loader=FileSystemLoader('templates'))

Expand Down Expand Up @@ -76,6 +77,17 @@ def render_GET(self, request):
redirect_url=canarydrop._drop['redirect_url']).encode('utf8')

if request.getHeader('Accept') and "text/html" in request.getHeader('Accept'):
if canarydrop._drop['type'] == 'secure_note':
now = datetime.datetime.now()
template = env.get_template('secure_note.html')
return template.render(
# note=canarydrop._drop['secure_note_text'],
note_ciphertext=canarydrop._drop['secure_note_ciphertext'],
initialization_vector=canarydrop._drop['secure_note_initialization_vector'],
now=now,
settings=settings
).encode('utf8')

if canarydrop['browser_scanner_enabled']:
template = env.get_template('browser_scanner.html')
return template.render(key=canarydrop._drop['hit_time'],
Expand Down Expand Up @@ -126,7 +138,7 @@ def render_POST(self, request):
safety_net = request.args.get('safety_net', [None])[0]
last_used = request.args.get('last_used', [None])[0]
additional_info = {'AWS Key Log Data': {k:v for k,v in request.args.iteritems() if k not in ['user_agent', 'ip']}}

self.dispatch(canarydrop=canarydrop, src_ip=src_ip, useragent=useragent, additional_info=additional_info)
return self.GIF

Expand Down
6 changes: 6 additions & 0 deletions httpd_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def render_POST(self, request):
'sql_server',
'my_sql',
'aws_keys',
'secure_note',
'signed_exe',
'fast_redirect',
'slow_redirect',
Expand Down Expand Up @@ -169,6 +170,11 @@ def render_POST(self, request):
response['Hostname'] = canarydrop.get_hostname()
response['Url_components'] = list(canarydrop.get_url_components())

if token_type == "secure_note":
canarydrop['secure_note_text'] = request.args['secure_note_text'][0]
canarydrop['secure_note_ciphertext'] = request.args['secure_note_ciphertext'][0]
canarydrop['secure_note_initialization_vector'] = request.args['secure_note_initialization_vector'][0]

response['Token'] = canarytoken.value()
response['Auth'] = canarydrop['auth']
response['Email'] = email
Expand Down
84 changes: 82 additions & 2 deletions templates/generate_new.html
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ <h3 class="text-muted">
<span id="selected_token">Select your token</span>
<input name="type" type="hidden">
<ul id="dropdown" class="dropdown">
<li data-type="secure_note" data-memo-placeholder="Password for the zip file"><a href="#" class="icon icon-secure-note"><span class="title">Secure note</span><div class="explanation">Get alerted when the link to your note is visited</div></a></li>
<li data-type="web" data-memo-placeholder="URL within Dropbox"><a href="#" class="icon icon-web"><span class="title">Web bug / URL token</span><div class="explanation">Alert when a URL is visited</div></a></li>
<li data-type="dns" data-memo-placeholder="Hostname in /etc/hosts of server X"><a href="#" class="icon icon-dns"><span class="title">DNS token</span><div class="explanation">Alert when a hostname is requested</div></a></li>
<li data-type="aws_keys" data-memo-placeholder="AWS keys placed on Jim's laptop"><a href="#" class="icon icon-aws"><span class="title">AWS keys</span><div class="explanation">Alert when AWS key is used</div></a></li>
Expand Down Expand Up @@ -149,6 +150,11 @@ <h3 class="text-muted">
<div class="field-optional field-signed_exe hidden">
{{ fileupload('signed_exe', text="Click to select an EXE or DLL for tokening") }}
</div>
<div class="field-optional field-secure_note hidden">
<textarea class="form-control" type="text" tabindex=3 name="secure_note_text" placeholder="The password for the zip file is 'hunter2'"></textarea>
<input type="hidden" name="secure_note_ciphertext">
<input type="hidden" name="secure_note_initialization_vector">
</div>
<div class="field-optional field-cloned_website hidden">
<input class="form-control" type="text" tabindex=3 name="clonedsite" placeholder="Domain of protected website (e.g. thinkst.com)">
</div>
Expand Down Expand Up @@ -263,6 +269,18 @@ <h3>Your Slow Redirect token is active!</h3>
</p>
</div>
</div>
<div class="result secure_note">
<h3>Your secure note is active!</h3>
<div class="artifacts">
<p>Copy this URL to your clipboard and use as you wish:</p>
<div class="form-control">
{{ inputcopy('result_secure_note', refresh=True) }}
</div>
</div>
<div class="advice">
<p>Remember, it gets triggered whenever someone requests the URL.</p>
</div>
</div>
<div class="result dns">
<h3>Your DNS token is active!</h3>
<div class="artifacts">
Expand Down Expand Up @@ -1066,6 +1084,17 @@ <h3>Your log4shell token is active!</h3>
$('#result_dns').val(data['Hostname']);
};

var _handleSecureNoteResponse = function(data) {
$('#result_secure_note').val(data['Url'] + "#secret_key=" + encodeURIComponent(document.secreykey_b64));
$('#result_secure_note').siblings('.refresh').on('click', function() {
url = [data['Url_components'][0].random(),
data['Url_components'][1].random(),
data['Token'],
data['Url_components'][2].random()].join('/');
$('#result_secure_note').val(url);
});
};

var _handleAWSKeysResponse = function(data){
$('#result_aws_keys').val('[default]'+
'\naws_access_key_id = '+ data['aws_access_key_id']+
Expand Down Expand Up @@ -1107,6 +1136,53 @@ <h3>Your log4shell token is active!</h3>
var _handleClonedWebsiteResponse = function(data) {
$('#result_cloned_website').val(data['clonedsite_js']);
}

function encryptText(plaintext, key, initialization_vector) {
let data = new TextEncoder().encode(plaintext);
return window.crypto.subtle.encrypt(
{
name: "AES-GCM",

//Don't re-use initialization vectors!
//Always generate a new iv every time your encrypt!
//Recommended to use 12 bytes length
iv: initialization_vector,

//Tag length (optional)
tagLength: 128, //can be 32, 64, 96, 104, 112, 120 or 128 (default)
},
key, //from generateKey or importKey above
data //ArrayBuffer of data you want to encrypt
)
.then(function(encrypted){
//returns an ArrayBuffer containing the encrypted data
return new Uint8Array(encrypted);
})
}

var _handleSecureNoteRequest = async function(form) {
let plaintext = $('textarea[name=secure_note_text]').val();
let key = await window.crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 128,
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["encrypt", "decrypt"] //can "encrypt", "decrypt", "wrapKey", or "unwrapKey"
);
let initialization_vector = window.crypto.getRandomValues(new Uint8Array(12));
let ciphertext = await encryptText(plaintext, key, initialization_vector);
let secretkey = new Uint8Array(await window.crypto.subtle.exportKey("raw", key));
ciphertext_b64 = btoa(String.fromCharCode.apply(null, ciphertext));
let initialization_vector_b64 = btoa(String.fromCharCode.apply(null, initialization_vector));
document.secreykey_b64 = btoa(String.fromCharCode.apply(null, secretkey));
$('input[name=secure_note_ciphertext]').val(ciphertext_b64);
$('input[name=secure_note_initialization_vector]').val(initialization_vector_b64);
let fd = new FormData(form[0]);

fd.set("secure_note_text", "[message was encrypted]"); // prevent plaintext note being sent to the server
return fd;
}
var _handleQrCodeResponse = function(data) {
$('#qrcode_png').siblings('a').prop('href', data['qrcode_png']);
$('#qrcode_png').siblings('a').prop('download', data['Token']+'.png');
Expand Down Expand Up @@ -1170,6 +1246,7 @@ <h3>Your log4shell token is active!</h3>

var _requestCallbacks = {'web_image': _handleWebImageRequest,
'cloned_website': _handleClonedWebsiteRequest,
'secure_note': _handleSecureNoteRequest,
'default': _handleDefaultRequest};

var _responseCallbacks = {'web': _handleWebResponse,
Expand All @@ -1186,6 +1263,7 @@ <h3>Your log4shell token is active!</h3>
'smtp': _handleSMTPResponse,
'sql_server': _handleSQLServerResponse,
'my_sql': _handleMySQLResponse,
'secure_note': _handleSecureNoteResponse,
'signed_exe': _handlerSignedExeResponse,
'fast_redirect': _handleFastRedirectResponse,
'slow_redirect': _handleSlowRedirectResponse,
Expand All @@ -1195,7 +1273,8 @@ <h3>Your log4shell token is active!</h3>
'default': _handleDefaultResponse
};

function processForm( e ){
async function processForm( e ){
e.preventDefault();
var type = $('input[name=type]').val();
var data;

Expand All @@ -1204,7 +1283,8 @@ <h3>Your log4shell token is active!</h3>
cb = _requestCallbacks['default'];
}

data = cb($(this));
data = cb($(this))
data = await Promise.resolve(data);

// Safari Web Browser can't handle input types of file to be empty fields and Edge can't handle incomplete formData fields, but doesn't mind if they are empty.
// Thus it is checked whether the browser is Edge and if so the data is left in its original format. If it is any other browser, all empty input fields
Expand Down
Loading