RouterControl is a new take on the old browser based attack that tricks the user into allowing exploits of saved passwords to access another site; in this case the "router". Kaminisky demonstrated the specific RouterControl feature at RSA in October 08. Various RouterControl attacks also use CSRF to own the router by repointing DNS to hostile servers so MITM attacks or DNS redirection can be easily created. http://ha.ckers.org/blog/20090120/persistent-cookies-and-dns-rebinding-redux/ Of course, as evidenced below, they could also give the router additional open ports and DMZ or whatever they liked and the poor private property owner would just see strange packet traffic and usually (due to lack of training with regards to security) never notice what is occurring, while being watched, tested, and essentially stalked in a strange cat/mouse game. Another wireless RouterControl goal would include loading Linux powered firmware that would attack nearby APs using brute-force password guessing techniques after association; of course this becomes less trivial if the AP is running WPA/WPA2. That would be more "wormlike". Essentially, own a device with CSRF and use it to own nearby APs. -- In the current successful example version of this "hack", you would find the following access history in your browser to your local router at say 192.168.1.1 for linux.js (which is a file not included in the firmware, ahem!). # linux.js var DEBUG = false; // false for release var separator = "\t"; // used for string=> multiple select list // ====================================================== Submit Functions function radioTable(fObj,radioObj,act_str) { if (radioSelectedIndex(radioObj) > -1) stdAction(fObj,act_str); else alert("No entry selected. \nClick a radio button to select an entry."); } function stdAction(fObj,act_str) { fObj.todo.value = act_str; dataToHidden(fObj); //submitDemo(fObj); fObj.submit(); } //========================================================= Data Transfer Functions function optionSelected(sel_obj) // return true or false { return (sel_obj.selectedIndex > -1 && sel_obj.selectedIndex < sel_obj.options.length) ? true : false; } function getSelIndex(sel_object, sel_text) { if (sel_text.length == 0) return 0; var size = sel_object.options.length; for (var i = 0; i < size; i++) { if (sel_object.options[i].value == sel_text) return(i); } for (var i = 0; i < size; i++) { if (sel_object.options[i].text == sel_text) return (i); } if (DEBUG) alert("DEBUG: " + sel_object.name + " (Select List) has invalid value " + sel_text + " Selecting 1st item instead"); return 0; // if no match } function getSelected(sel_obj) // single select. Returns value. If value blank, return text { var index = sel_obj.selectedIndex; if (index >= 0) return (sel_obj.options[index].value != "") ? sel_obj.options[index].value : sel_obj.options[index].text; else return ""; } function getMultiSelected(sel_obj) // multi select. Always use text, not value { var size = sel_obj.options.length; var i; var str = ""; if(isNaN(size)) return str; if(size == 0) return str; str = separator; for(i = 0; i < size; i++) if (sel_obj.options[i].selected) str+= sel_obj.options[i].text + separator; return str; } function setSelected(sel_obj,list) // list has multiple items from select obj { var selSize = sel_obj.options.length; var startTextPos; var startValuePos; var textChar; var valueChar; for ( var i =0 ; i < selSize; i++) { startTextPos = -1; startValuePos = -1; sel_obj.options[i].selected = false; startTextPos = list.indexOf(separator + sel_obj.options[i].text + separator); if(sel_obj.options[i].value.length > 0) startValuePos = list.indexOf(separator + sel_obj.options[i].value + separator); if (startTextPos > -1) sel_obj.options[i].selected = true; if (startValuePos > -1) sel_obj.options[i].selected = true; } } function radioSelectedIndex(radio_object) // index of selected item, -1 if none { if (!radio_object) return -1; var size = radio_object.length; if(isNaN(size)) { if(radio_object.checked == true) return 0; else return -1; } for (var i = 0; i < size; i++) { if(!(radio_object[i])) return (radio_object.checked) ? 0 : -1; if (radio_object[i].checked) return(i); } if(radio_object.checked == true) return 0; else return -1; } function getRadioCheckedValue(radio_object) // value of selected item, "" if none { var index = 0; if (!radio_object) return ""; var size = radio_object.length; if(isNaN(size)) { if (radio_object.checked == true) return radio_object.value; else return ""; } for (var i = 0; i < size; i++) { if(!(radio_object[i])) continue; if (radio_object[i].checked == true) return(radio_object[i].value); } if (radio_object.checked == true) return radio_object.value; else return ""; } function getRadioIndex(radio_object, checked_value) // find index matching checkecd_value, 0 if no match { if (!radio_object) return 0; if(radio_object.value == checked_value) return 0; var size = radio_object.length; if(isNaN(size)) return 0; for (var i = 0; i < size; i++) { if(!(radio_object[i])) continue; if (radio_object[i].value == checked_value) return i; } if (DEBUG) alert("DEBUG: " + radio_object.name + " (Radio button) has invalid value " + checked_value + " Selecting 1st item instead"); return 0; // if no match } function getvalue(field_obj) { var field_type = field_obj.type; if (field_type == "text" || field_type == "password" || field_type == "hidden" || field_type == "textarea") return field_obj.value; else if (field_type == "select-one") return getSelected(field_obj); else if (field_type == "select-multiple") return getMultiSelected(field_obj); else if (field_type == "checkbox") return (field_obj.checked) ? "enable" : "disable" ; else if (field_type == "radio") return getRadioCheckedValue(field_obj); else if (field_obj.length > 0 ) // must be radio, but type shows as undefined return getRadioCheckedValue(field_obj); else return field_obj.value; } function net_mask_test(net_mask,lan_ipaddr,wan1,wan2,wan3,wan4) { var lan_all; var lan_len; var lan_tmp; var mask_all; var mask_len; var mask_tmp; lan_all=lan_ipaddr.value; mask_all=net_mask.value; //ip1 mask_len=mask_all.length; mask_tmp=mask_all.indexOf("."); lan_len=lan_all.length; lan_tmp=lan_all.indexOf("."); if( (mask_all.substring(0,mask_tmp) & wan1.value) != (mask_all.substring(0,mask_tmp) & lan_all.substring(0,lan_tmp)) ) return 0; //ip2 mask_all=mask_all.substring(mask_tmp+1,mask_len); mask_len=mask_all.length; mask_tmp=mask_all.indexOf("."); lan_all=lan_all.substring(lan_tmp+1,lan_len); lan_len=lan_all.length; lan_tmp=lan_all.indexOf("."); if( (mask_all.substring(0,mask_tmp) & wan2.value) != (mask_all.substring(0,mask_tmp) & lan_all.substring(0,lan_tmp)) ) return 0; //ip3 mask_all=mask_all.substring(mask_tmp+1,mask_len); mask_len=mask_all.length; mask_tmp=mask_all.indexOf("."); lan_all=lan_all.substring(lan_tmp+1,lan_len); lan_len=lan_all.length; lan_tmp=lan_all.indexOf("."); if( (mask_all.substring(0,mask_tmp) & wan3.value) != (mask_all.substring(0,mask_tmp) & lan_all.substring(0,lan_tmp)) ) return 0; //ip4 mask_all=mask_all.substring(mask_tmp+1,mask_len); lan_all=lan_all.substring(lan_tmp+1,lan_len); if( (mask_all & wan4.value) != (mask_all & lan_all) ) return 0; else return 1; } function ip1to4(ipaddr,ip1,ip2,ip3,ip4) { // alert("name: " + ipaddr.name); var len; var tmp; var all; all=ipaddr.value; //ip1 len=all.length; tmp=all.indexOf("."); ip1.value=all.substring(0,tmp); //ip2 all=all.substring(tmp+1,len); len=all.length; tmp=all.indexOf("."); ip2.value=all.substring(0,tmp); //ip3 all=all.substring(tmp+1,len); len=all.length; tmp=all.indexOf("."); ip3.value=all.substring(0,tmp); //ip4 all=all.substring(tmp+1,len); ip4.value=all; } function ip4to1(ipaddr,ip1,ip2,ip3,ip4) { if (ip1.value.length>0) ipaddr.value=ip1.value+"."+ip2.value+"."+ip3.value+"."+ip4.value; else ipaddr.value=""; } function mac1to6(macaddr,mac1,mac2,mac3,mac4,mac5,mac6) { var len; var tmp; var all; all=macaddr.value; //mac1 len=all.length; tmp=all.indexOf(":"); mac1.value=all.substring(0,tmp); //mac2 all=all.substring(tmp+1,len); len=all.length; tmp=all.indexOf(":"); mac2.value=all.substring(0,tmp); //mac3 all=all.substring(tmp+1,len); len=all.length; tmp=all.indexOf(":"); mac3.value=all.substring(0,tmp); //mac4 all=all.substring(tmp+1,len); len=all.length; tmp=all.indexOf(":"); mac4.value=all.substring(0,tmp); //mac5 all=all.substring(tmp+1,len); len=all.length; tmp=all.indexOf(":"); mac5.value=all.substring(0,tmp); //mac6 all=all.substring(tmp+1,len); mac6.value=all; } function mac6to1(macaddr,mac1,mac2,mac3,mac4,mac5,mac6) { if (mac1.value.length>0) macaddr.value=mac1.value+":"+mac2.value+":"+mac3.value+":"+mac4.value+":"+mac5.value+":"+mac6.value; else macaddr.value=""; } function dataToVisible(form_obj) // both hidden & visible fields in same form { var form_size = form_obj.elements.length; var sourceField; var last_name; var radioIndex; var baseRef; for (var i = 0; i < form_size; i++) { if (form_obj.elements[i].name.substr(0,3)=="c4_") { baseRef = "form_obj." + form_obj.elements[i].name.substr(3); ip1to4(form_obj.elements[i], eval(baseRef+"1"), eval(baseRef+"2"), eval(baseRef+"3"), eval(baseRef+"4")); } if (form_obj.elements[i].name.substr(0,3)=="c6_") { baseRef = "form_obj." + form_obj.elements[i].name.substr(3); mac1to6(form_obj.elements[i], eval(baseRef+"1"), eval(baseRef+"2"), eval(baseRef+"3"), eval(baseRef+"4"), eval(baseRef+"5"), eval(baseRef+"6")); } sourceField = eval("form_obj.h_" + form_obj.elements[i].name); if(!(sourceField)) continue; if(sourceField.value == "") continue; if (form_obj.elements[i].type=="select-one") form_obj.elements[i].selectedIndex = getSelIndex(form_obj.elements[i], sourceField.value); if (form_obj.elements[i].type=="select-multiple") setSelected(form_obj.elements[i],sourceField.value); if (form_obj.elements[i].type == "checkbox") form_obj.elements[i].checked = (sourceField.value == "enable"); if (form_obj.elements[i].type == "radio") { if (last_name == form_obj.elements[i].name) continue; // already done this one last_name = form_obj.elements[i].name; radioIndex = getRadioIndex(form_obj.elements[form_obj.elements[i].name],sourceField.value); if(form_obj.elements[form_obj.elements[i].name][radioIndex]) form_obj.elements[form_obj.elements[i].name][radioIndex].checked = true; else form_obj.elements[form_obj.elements[i].name].checked = true; } } } function dataToHidden(form_obj) // both hidden & visible fields in same form { var form_size = form_obj.elements.length; var destField; var last_name; var radioIndex; var baseRef; for (var i = 0; i < form_size; i++) { if (form_obj.elements[i].name.substr(0,3)=="c4_") { baseRef = "form_obj." + form_obj.elements[i].name.substr(3); ip4to1(form_obj.elements[i], eval(baseRef+"1"), eval(baseRef+"2"), eval(baseRef+"3"), eval(baseRef+"4")); } if (form_obj.elements[i].name.substr(0,3)=="c6_") { baseRef = "form_obj." + form_obj.elements[i].name.substr(3); mac6to1(form_obj.elements[i], eval(baseRef+"1"), eval(baseRef+"2"), eval(baseRef+"3"), eval(baseRef+"4"), eval(baseRef+"5"), eval(baseRef+"6")); } destField = eval("form_obj.h_" + form_obj.elements[i].name); if(!(destField)) continue; if (form_obj.elements[i].type=="select-one") destField.value = getSelected(form_obj.elements[i]); if (form_obj.elements[i].type=="select-multiple") destField.value = getMultiSelected(form_obj.elements[i]); if (form_obj.elements[i].type == "checkbox") destField.value = (form_obj.elements[i].checked) ? "enable" : "disable"; if (form_obj.elements[i].type == "radio") { if (last_name == form_obj.elements[i].name) continue; // already done this one last_name = form_obj.elements[i].name; destField.value = getRadioCheckedValue(form_obj.elements[form_obj.elements[i].name]); } } } function ipsAppdataToHidden(form_obj) // both hidden & visible fields in same form { var form_size = form_obj.elements.length; var index; for (var i = 0; i < form_size; i++) { if (form_obj.elements[i].name.substr(0,7)=="ipsano_" && form_obj.elements[i].checked) { form_obj.ipsano.value += form_obj.elements[i].name.substr(7); form_obj.ipsano.value += ","; form_obj.ipsano.value += form_obj.elements[i].value == "enable" ? "1;" : "0;"; } if (form_obj.elements[i].name.substr(0,6)=="ipsim_" && form_obj.elements[i].checked) { form_obj.ipsim.value += form_obj.elements[i].name.substr(6); form_obj.ipsim.value += ","; form_obj.ipsim.value += form_obj.elements[i].value == "enable" ? "1;" : "0;"; } if (form_obj.elements[i].name.substr(0,7)=="ipsp2p_" && form_obj.elements[i].checked) { form_obj.ipsp2p.value += form_obj.elements[i].name.substr(7); form_obj.ipsp2p.value += ","; form_obj.ipsp2p.value += form_obj.elements[i].value == "enable" ? "1;" : "0;"; } } } // =================================== Development ======================== function show_data(form_obj) // shows form information - used only for debugging { var form_size = form_obj.elements.length; var debug_win = window.open("","debug","width=540,height=360,menubar=yes,scrollbars=yes,resizable=yes"); with(debug_win.document) { open(); writeln('Debugging Window'); writeln('

Form being submitted

'); writeln('

Form Name: ' + form_obj.name); writeln('
Form Action: ' + form_obj.action); writeln('
Form Target: ' + form_obj.target); writeln('

Form Data

Following table shows ALL fields, even if not submitted.

'); writeln('

'); for (var i = 0; i < form_size; i++) { writeln(''); writeln(''); writeln(''); } writeln('
Field NameTypeValue
' + form_obj.elements[i].name + '' + form_obj.elements[i].type + ''); if ((form_obj.elements[i].type=="select-one") || (form_obj.elements[i].type=="select-multiple")) writeln('Selected item: ' + form_obj.elements[i].options[form_obj.elements[i].selectedIndex].text); else writeln(form_obj.elements[i].value); if ((form_obj.elements[i].type == "radio") && (form_obj.elements[i].checked)) write(' (Selected)'); if ((form_obj.elements[i].type == "checkbox") && (form_obj.elements[i].checked)) writeln(' (Checked)'); writeln('
'); close(); } } function submitDemo(form_obj) { show_data(form_obj); } function writeForm(form_obj) // shows form information - used for developement { var form_size = form_obj.elements.length; var hfieldStr = ""; var hdemoStr = ""; var last_name = ""; var i; var j; var debug_win = window.open("","form_def","width=680,height=360,menubar=yes,scrollbars=yes,resizable=yes"); with(debug_win.document) { open(); writeln('Form Definition Window'); writeln('

Form Name: ' + form_obj.name); writeln('
Form Action: ' + form_obj.action); writeln('
Form Target: ' + form_obj.target); writeln('

Following table shows checkboxes, radio buttons, selects fields.

\n
');
		for (var i = 0; i < form_size; i++)
		{
			if (form_obj.elements[i].type=="select-one")
			{
				write(form_obj.elements[i].type + " : " +
form_obj.elements[i].name + "   ");
				for(j=0; j < form_obj.elements[i].options.length; j++)
					write(" [" + j + "]" + form_obj.elements[i].options[j].text);
				writeln();
				hfieldStr += '\n';
				hdemoStr += '\n';
			}

			else if (form_obj.elements[i].type=="select-multiple")
			{
				write(form_obj.elements[i].type + " : " +
form_obj.elements[i].name + "   ");
				for(j=0; j < form_obj.elements[i].options.length; j++)
					write(" [" + j + "]" + form_obj.elements[i].options[j].text);
				writeln();
				hfieldStr += '\n';
				hdemoStr += '\n';
			}

			else if (form_obj.elements[i].type == "radio")
			{
				if (last_name == form_obj.elements[i].name)
					continue; // already done this one
				else
				{
					last_name = form_obj.elements[i].name;
					write(form_obj.elements[i].type + " : " +
form_obj.elements[i].name + "   ");
					for (var j = 0; j <
form_obj.elements[form_obj.elements[i].name].length; j++)
						write(" [" + j + "]" +
form_obj.elements[form_obj.elements[i].name][j].value);
					writeln();
					hfieldStr += '\n';
					hdemoStr += '\n';
				}
			}

			else if (form_obj.elements[i].type == "checkbox")
			{
				writeln(form_obj.elements[i].type + " : " + form_obj.elements[i].name);
				hfieldStr += '\n';
				hdemoStr += '\n';
			}
			else ;  // no action
		}
		writeln('\n' + hdemoStr + '\n');
		writeln('\n');
		writeln('
'); close(); } } Of course if you see this file, it does not mean that they were able to RUN this successfully, but you can be assured that they did write to your router. One of the first things you might notice is that your form input fields are missing or strangely disabled in your router. There are a great many versions of this exploit. Payload could be delivered via email, web browsing or URL. A firmware upgrade will fix the router (temporarily); be sure you DO NOT save your router user and password after you get finished rebuilding it all. And incidently while rebuilding, you might also assume that they now pwn your browser also. Don't bother to look - it's going to be proxied via XSS tunnel to someplace like Google.com (you will never see the cracker): http://www.obnosis.com/motivatebytruth/hackers.jpg Might check to see if you have a trillion 127.0.0.1 sessions also? For great descriptions of router hacking check here: http://www.gnucitizen.org/projects/router-hacking-challenge/ For description of how to identifiy and mitigate CSRF: http://labs.securitycompass.com/index.php/2009/05/15/the-true-danger-of-xss-and-csrf/ These are the techniques that the NSA can deploy via trivial scripts and entrapment; not even seasoned security experts are above this, there WILL BE a time when you disable Javascript via socially engineered trust? (623)239-3392 (503)754-4452 www.obnosis.com --------------------------------------------------- PLUG-discuss mailing list - PLUG-discuss@lists.plug.phoenix.az.us To subscribe, unsubscribe, or to change your mail settings: http://lists.PLUG.phoenix.az.us/mailman/listinfo/plug-discuss