Frittz!Box 3273 Mediaserver Hacking

Since my D-Link router kicked the bucket some days ago I had to buy a new router, I heard good stuff about Fritz products and decided to go with a Frittz!Box 3273 which is basicly their low-end product.

One thing I totally did not expect was the support for running the router as a UPnP server. It can either serve data from devices attached to two local USB ports or from external WebDAV resources. This gives me the chance to power down my current Servioo based server and save some power.

Unfortunately, there are known issues around the media server having to do with not refreshing properly if the contents of a mounted USB medium change. I digged arounnd some internet posts but did not come to a good solution and peeked into it myself.

Here's what I found out. The solution is not optimal and hopefully obsolete when AVM puts out some update in the future. Apart from the solution itself I found out some interesting things, you might be interested in as well.

Manually Forcing a Refresh

The router maintains a database of files present on a storage medium which is called fritznasdb_part.db3 and saved in the medium itself. As the post linked above noted, this is a SqlLite database and you can peek into it with tools like SQLLiteManager. Some people suggested that just deleting this file should do the job, however when I tried it did not get recreated afterwards, however it did get created, when I navigated to the web-based file browser.

So hat I take away from this is, if I find a ways to delete the file and then automatically call the file browser, I would have a way to refresh the media server repository without the use of manual steps.

Automating the Solution

Automating the thing turned out to be a little more challenging than I originally expected. This has to do with the way logging in works. I took a trace with Fiddler to see what get's transmitted to the server when I enter my password and hit the login button.

What we see here is the first request being the POST, that submits the password and the second the home screen being loaded. The 303 code (see other) returned from the POST performs a redirect which specifies to load the home screen afterwards.

HTTP/1.1 303 See Other
Connection: Keep-Alive
Content-Length: 0
Location: http://192.168.188.1/home/home.lua?sid=771b46e69887ce56
Keep-Alive: timeout=60, max=300

Apart from the 303 code instead of 301 (object moved), this is pretty straight forward forms authentication. Perhaps there is one speciality - most of the times you see a session token returned in the response as a cookie, here they use a querry string parameter called sid to keep the session state.

Peeking into the request, I expected the password to be submitted with a form post, however this was not the case:

POST http://192.168.188.1/login.lua HTTP/1.1
Host: 192.168.188.1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://192.168.188.1/login.lua?page=/home/home.lua&logout=1&sid=c55e7952e0b56266
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 102

response=9331d731-591225XXXXXXXXXXXXXXXX&page=%2Fhome%2Fhome.lua&username=&site_mode=classic

The only way that this is possible is by using Javascript on the client to preprocess the post. The only part of the post where the password could be embedded is the red marked one. So - to automate a login I would need to have some understanding of what is going on here. For Javascript debugging I can highly suggest the Chrome Developer Tools, To get a hook into the client application I break at the form submit event and see, what happens.

A nice way to do that with chrome is by selecting the form element on the page and having a look at the event listeners panel.

Clicking on the red marked hyperlink will directly take you to the code that runs when logging in.

var dot_pass = makeDots(jxl.getValue("uiPass"))
var resp = g_challenge + "-" + dot_pass;
jxl.setValue("uiResp", g_challenge + "-" + hex_md5(resp));

The makeDots function is quite straightforward - it replaced all special characters in the password with dots. g_Challenge contains a hex string (e.g. 32745db9), which is then concatenated with the password (e.g. 32745db9-password). After that the method hex_md5 is used to process this string, it's output is concatenated to the challenge again and the result is written to the hidden form field uiResp (which is the response field we saw in the fiddler trace).

Each time the page is loaded, g_Challenge is different. This prevents us from replaying a request at a later time. The hex_md5 function is part of the md5.js library as you can see in the source when tracing into it. Unfortunately, the way the MD5 is calculated does not conform with anything outside of this function I guess - because JavaScript has no real good functionality to convert between bytes and strings and deal with unicode, it does some proprietary stuff to calculate the hash that would take some time to reproduce with a high error rate. So, I came up with the idea of just running the function inside my code.

There is an open source project called Javascript.NET that allows you to execute Javascript code inside your .NET application. Using it is straightforward, you create a context for your Javascript application, pass in parameters and extract their values afterwards, here's the code I use.

var challenge = "32745db9";
string scrambledText;
using (JavascriptContext context = new JavascriptContext())
{
    StringBuilder script = new StringBuilder();
    script.Append(new StreamReader(typeof(Program).Assembly.GetManifestResourceStream("FritzRefresh.md5.js")).ReadToEnd());
    script.Append(new StreamReader(typeof(Program).Assembly.GetManifestResourceStream("FritzRefresh.main.js")).ReadToEnd());
    context.SetParameter("rawText", challenge + "-" + password);
    context.Run(script.ToString());
    scrambledText = (string)context.GetParameter("rawText");
}

As you see, i load up the md5.js library and a custom one called main.js which are both embedded resources in the application. I set the value of the parameter rawText by concatenating challenge and password, just the way the Fritz!Box does it. After I run the script I extract the value from the parameter. Now let's take a look into main.js:

rawText = hex_md5(rawText);

Well - that was easy :P Ok, with this in place we are able to perform the password-scrambling ourselves. What we have to do now is to get the challenge. For this I make a request to the login page using HTTP get and extract the g_Challenge value via a regular expression:

var webClient = new WebClient();
var content = webClient.DownloadString("http://" + address + "/logincheck.lua");
var challengeRegex = new Regex(@"g_challenge = ""(.+)""");
var contentMatch = challengeRegex.Match(content);
var challenge = contentMatch.Groups[1].Value;

With the challenge being parsed and hex_md5 having generated the hash, we can push out the HTTP post to get to our session token.

HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://" + address + "/login.lua");
request.AllowAutoRedirect = false;
request.Method = "post";
request.ContentType = "application/x-www-form-urlencoded";
using (var sw = new StreamWriter(request.GetRequestStream()))
{
    sw.Write("response=" + challenge + "-" + scrambledText + "&page=%2Fhome%2Fhome.lua&username=&site_mode=classic");
    sw.Flush();
}
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
var sessionToken = new Uri(response.Headers["Location"]).Query.Substring("?sid=".Length);

Note the we need to disable AllowAutoRedirect, because otherwise the web client would follow the redirect and we could not look at the response for our HTTP post but only at the response of the hone page. The session token is extracted in the last line by looking at the sid query parameter. Ok, at this point we have a valid session and can perform the navigation to the file browser inside of it. To find out the url we're interested in, I did another Fiddler trace.

You see, the request contains the sid again. This is the one we got in the prior step. In addition there is an parameter called tXXXX, which I assume to be a random token to prevent caching. I used random values here and did not get problems.
So here is the last step that causes the box to actually refresh:

var randomToken = Environment.TickCount.ToString();
var result = webClient.DownloadString("http://" + address + "/nas/ajax_files_browse.lua?sid=" + sessionToken + "&dir=%2f&site=files&site_mode=classic&browse_mode=type:directory&start_entry=1&total_file_count=0&xhr=1&t1" + randomToken + "=nocache");

After running this request, the fritznasdb_part.db3 file is recreated. One small nuisance I cannot resolve is, that when using my smart TV, i have to navigate to the folder twice to see the data - but hey - better than nothing.

I hope you found this post interesting. I attached the code to it, so you can play with it yourself if you like.

Cheers

fritz-refresh.zip.zip (6,16 mb)

About the author

for comments and suggestions contact:

 

Month List