D-Link SharePort Web Access Authentication Bypass
D-Link SharePort Web Access Authentication Bypass
Vulnerability Description
SharePort Web Access is a feature available to most D-Link wireless home routers with a USB port.
It is enabled by default.
Tutorial from its website shows that just insert your USB Flash Drive into your router, then you will be able to access it from the Internet by visit a URL like http://[ip]:8181
.
Theoretically you have to be authorized by entering your router’s administrator username and password.
However, D-Link screws it up pretty badly.
First: If you have not changed them from the defaults, the username should be Admin, and the password field should be left blank.
Second: The authentication can be bypassed directly. Just visit folder_view.php
or category_view.php
or any valid page in this path and authentication is not required, including even logininfo.xml
.
In some cases, visit http://[ip]:[port]/webaccess/folder_view.php
instead http://[ip]:[port]/folder_view.php
.
Vulnerability Scope
At least these wireless router models below are affected, each with its latest firmware installed.
Model | Revision | Firmware | Note |
---|---|---|---|
DIR-868L | REVB | 2.03 | Latest |
DIR-885L | REVA | 1.20 | Latest |
DIR-895L | REVA | 1.21 | Latest |
Vulnerability Analysis
So, how the authentication fails ?
Take http://[ip]:[port]/folder_view.php
for example.
Authentication Functions
folder_view.php
is actually a HTML with JavaScript, nothing PHP here.
On line 719
<body onLoad="load_value();get_login_info()">
get_login_info()
is supposed to do the authentication .
get_login_info()
function get_login_info(){
var xml_request = new XMLRequest(get_login_info_result);
var para = "request=get_login_info";
xml_request.exec_webfile_cgi(para);
}
XMLRequest()
/**
* XMLRequest( ) - Constructor for building XMLRequest Object
* Parameter(s) :
* onReqComp : the function that you want to perform after receive response from the web server
*
* Variable(s) :
* http_req : user custom function for performing post response.
* onReqComp : user custom function for performing post response.
*
**/
function XMLRequest(onReqComp){
this.http_req = create_http_request();
this.onReqComp = onReqComp;
}
/**
* XMLRequest.prototype - Prototype of XMLRequest Object
*
* Data members:
* READY_STATE_***: ready state constants
*
* Methods:
* loading_xml() : get remote xml document
* exec_cgi() : requesting server performing CGI command
* get_login_level() : get user's login level
* onReadyState() : callback function while readystate changing
**/
XMLRequest.prototype = {
READY_STATE_UNINITIALIZED : 0,
READY_STATE_LOADING : 1,
READY_STATE_LOADED : 2,
READY_STATE_INTERACTIVE : 3,
READY_STATE_COMPLETE : 4,
loading_xml: function(which_url){
//...
},
exec_cgi: function(para){
//...
},
exec_webfile_cgi: function(para){
var req_url = "logininfo.xml?" + para;
var obj = this;
if (this.http_req){
this.http_req.onreadystatechange = function() {obj.onReadyState.call(obj)};
this.http_req.open('GET', req_url, true);
this.http_req.setRequestHeader('Content-length', para.length);
this.http_req.setRequestHeader("Connection", "close");
this.http_req.send(null);
return true;
}
return false;
},
exec_auth_cgi: function(para){
//...
},
json_cgi: function(para){
//...
},
get_login_level: function(){
//...
},
onReadyState: function(){
//...
}
}
get_login_info_result()
function get_login_info_result(http_req){
//...
var my_xml = http_req.responseXML;
if (check_user_info(my_xml.getElementsByTagName("redirect_page")[0])){
storage_user.put("id", get_node_value(my_xml, "user_name"));
storage_user.put("tok", get_node_value(my_xml, "user_pwd"));
load_webfile_settings();
}
}
check_user_info()
/**
* check_user_info() : to check if the user has login yet
*
* Parameter(s) :
* redirect_page : a XML's element object which contains a redirect page
*
* Return : True or False
*
**/
function check_user_info(redirect_page){
var which_page;
if (redirect_page != null){
which_page = redirect_page.firstChild.nodeValue;
location.href = html_obj.get_value(which_page);
return 0;
}
return 1;
}
How It Failed
When get_login_info()
GET
http://[ip]:[port]/logininfo.xml?request=get_login_info
We get
<?xml version="1.0"?>
<root>
<user_name>admin</user_name>
<user_pwd>t</user_pwd>
<volid>1</volid>
</root>
In get_login_info_result()
var my_xml = http_req.responseXML;
my_xml
is actually the XML content above.
my_xml.getElementsByTagName("redirect_page")
should return
HTMLCollection []
length: 0
__proto__: HTMLCollection
And if we should try to take its first element, undefined
is returned.
In get_login_info_result()
if (check_user_info(my_xml.getElementsByTagName("redirect_page")[0])){
storage_user.put("id", get_node_value(my_xml, "user_name"));
storage_user.put("tok", get_node_value(my_xml, "user_pwd"));
load_webfile_settings();
}
If check_user_info(my_xml.getElementsByTagName("redirect_page")[0])
is True
, load_webfile_settings()
will be executed, which means user is authorized to visit this page folder_view.php
.
However, in check_user_info()
function check_user_info(redirect_page){
var which_page;
if (redirect_page != null){
which_page = redirect_page.firstChild.nodeValue;
location.href = html_obj.get_value(which_page);
return 0;
}
return 1;
}
If element redirect_page = my_xml.getElementsByTagName("redirect_page")[0]
does not exist, 1 is returned, and user is authorized to proceed !
Summary
The authentication procedure for D-Link SharePort Web Access basically fails at every aspects.
- Frontend authentication is not very reliable;
- Name your variable wisely, or you will most likely pay for it.