OUR LINK-CLICKING CSRF VICTIM ROBOT

Jacob Thompson, Independent Security Evaluators

Over the past year, ISE has brought our SOHOpelessly Broken router hacking contest to DEF CON, DerbyCon, Toorcon, and BSides DC. ISE started the contest to shine light on the need for manufacturers to better secure small office/home office (SOHO) devices; our thought was that by demonstrating the vulnerabilities first-hand, we could help manufacturers recognize that SOHO devices are highly vulnerable to malicious compromise, thus inspiring action and change. Among the contest’s tracks is a live capture-the-flag competition, in which contestants research known vulnerabilities and use them to attack real routers running on a test network. Cross-site request forgery (CSRF) is a common attack against the web interfaces of embedded devices. CSRF occurs when an adversary tricks the victim into clicking a link that leads to an attack page while simultaneously logged in to the vulnerable device. The attack page generates and sends malicious HTTP requests to the device, reconfiguring it without the victim’s knowledge or authorization. For the contest to be successful, we needed to automate the process of tricking a user into becoming a victim of a CSRF attack; the result is our link-clicking CSRF victim robot. This white paper describes the design and implementation of the resulting software.

Introduction

In 2014, ISE started the SOHOpelessly Broken router hacking contest to shine light on the need for manufacturers to better secure small office/home office (SOHO) devices; our thought was that by demonstrating the vulnerabilities first-hand, we could help manufacturers recognize that SOHO devices are highly vulnerable to malicious compromise, thus inspiring action change.

To facilitate the router hacking contest, we needed to find a way to allow our participants to launch cross-site request forgery (CSRF) attacks. Our ultimate goal was to develop an automated program that simulated the act of a hypothetical LAN-side router user clicking malicious links. CSRF occurs when a Web page loaded from one domain controlled by the attacker sends a malicious HTTP request to another domain, such as a router’s administration interface. While the browser’s same-origin policy does prevent the attacker’s code from obtaining the response to this request (as the attacker’s and router’s origins are different), the router nonetheless receives the request. If the user is actively logged in, and the router’s Web interface code has specifically been designed to detect and block CSRF, the router acts upon this fraudulent request. Depending upon other defenses present in the router, if any, CSRF can be used to change the administrator’s password, enable remote administration, or add new port forwarding entries to the router’s configuration.

We set up our contest network by assigning each router’s administrative interface a password unknown to the contestants, and then connecting one LAN port of each router to a central switch. The contestants, as well as our CSRF victim machine, were also connected to the same switch. The goal was to allow the contestants to trick a “user” into clicking an arbitrary link while logged into all of the routers’ interfaces and simultaneously preserving the secrecy of the router passwords, as well as the success or failure of the attack—much like a real-world scenario. To accomplish this, we needed the CSRF victim machine to provide the following functionality:

  1. Keep the Firefox Web browser open and logged in to each router’s administrative interface. As some of the interfaces were set to time out automatically after login, this includes periodically reloading each interface to prevent the inactivity timer from expiring.
  2. Provide a Web interface to which the contestants could submit their malicious links.
  3. Keep the Firefox Web browser open and logged in to each router’s administrative interface. As some of the interfaces were set to time out automatically after login, this includes periodically reloading each interface to prevent the inactivity timer from expiring.
  4. Upon receiving a malicious link, open the link in the same browser session in which the "user" is logged in to each router.
  5. After a reasonable interval, close the malicious page and wait for the next link to be submitted.

We accomplished this using a laptop running CentOS 6. Below, we describe each goal, discuss how we accomplished them, and provide sample code.

Maintain Logged-In Sessions to Each Router

Our contest contained 10 routers, and we needed to set up a browser session that simulated what would occur if a user manually opened 10 separate windows and logged in to one router’s administrative interface in each window. A handful of routers turned out to perform authentication at the client side, so we did not need to take any steps to log in to routers (lucky for us, unfortunate for the users of those routers). Most routers use HTTP Basic authentication to secure access to their administrative interfaces.

The routers employing HTTP Basic authentication require the browser to submit an Authorization header with each request containing a base64-encoded username and password, and if this header is missing or incorrect, they return an HTTP 401 Unauthorized error. Normally, this causes the browser to issue an authorization dialog box, which we wanted to avoid, as we were trying to automate the process of remaining logged in to all of the routers. In fact, HTTP Basic authentication credentials can also be encoded in a URL (in the form http://username:password@domain.example.com/page.html), a feature rarely used today outside of phishing attacks. We leveraged this fact to create a page, one .html (shown in Figure 1), which, when displayed in the browser, causes the user to be automatically logged in to the router 192.168.10.1 with the username admin and password RouterOnePassword.

A few routers used more sophisticated techniques for authentication, such as an HTTP POST form submission, that, when received, causes the router to issue a session cookie (assuming the credentials are valid). After designing one HTML page per router, we then created a page (see Figure 2) that loads each of these per-router pages. It also automatically refreshes every 300 seconds (5 minutes) in an attempt to avoid any timeout features in the routers’ interfaces.

1: <img src="http://admin:RouterOnePassword@192.168.10.1/adm/status.asp">

Figure 1. When the browser encounters this IMG tag, it automatically attempts to log in to the router 192.168.10.1 using Basic authentication with our supplied credentials.

 1: <html>
 2: <head>
 3: <title>KeepLoggedIn</title>
 4: <meta http-equiv="Refresh" content="300">
 5: </head>
 6: <body>
 7: <iframe src="1.html"></iframe>
 8: <iframe src="2.html"></iframe>
 9: <iframe src="6.html"></iframe>
10: <iframe src="7.html"></iframe>
11: <iframe src="8.html"></iframe>
12: <iframe src="10.html"></iframe>
13: </body>
14: </html>
15: 

Figure 2. Our per-router authentication pages are all automatically loaded and refreshed every five minutes.

Accepting Malicious Link Submissions from the Contestants

We used a bare-bones PHP Web application to accept malicious link submissions from our contestants. It consists of a static HTML page as shown in Figure 3. The page shows a single field, which allows the attacker to specify any link he/she desires to open in a browser session while simultaneously logged in to each router.

When the user submits the form, it is sent to a back-end PHP page (see Figure 4). This back end simply opens a pre-created FIFO (named pipe) on the file system, writes the link to the FIFO, closes it, and then returns a status back to the user. Our link-clicking script listens on the far end of the FIFO and performs the actual clicking of the link. We split the components in this way so that the server-side PHP code and the link-clicking script could run under separate user accounts.

 1: <html>
 2: <head>
 3: <title>Link Clicker</title>
 4: </head>
 5: 
 6: <body>
 7: <form id="link_submission" action="click.php" method="post">
 8: <table>
 9:   <tr>
10:     <td style="vertical-align:text-top;">Link:</td>
11:     <td>
12:       <input form="link_submission" rows="7" cols="45" id="link" 
13:              name="link" value="http://">
14:     </td>
15:   </tr>
16:   <tr>
17:     <td colspan=2 height=50 style="text-align:center;">
18:       <input style="width:150; height:25;" type="submit" value="Submit">
19:     </td>
20: </tr>
21: </table>
22: </form>
23: </body>
24: </html>

Figure 3. Link submission page front end.

 1: <?php
 2:         
 3:         $fifo_file = "/usr/local/share/routers/fifo";
 4:         $fifo_handle = fopen($fifo_file, "a");
 5:         $link = $_POST["link"] . "\n";
 6: 
 7:         fwrite($fifo_handle, $link);
 8:         fclose($fifo_handle);
 9:         
10: ?>
11: Link clicked
12: 

Figure 4. Link submission page back end.

Link-Clicking Script

Our Web application performs the work of collecting malicious links from contestants, but the contest needs a simulated user to follow the link. The link-clicking script receives links from the Web application and opens them one-by-one inside a browser session in which the “user” is also logged in to each router. The script (shown in Figure 5) reads URLs from a pre-created FIFO, one per line, and then uses Firefox’s remote option to open a new browser window to display the specified link.

To make it easier to track when a malicious link is currently open, we previously reconfigured Firefox browser preferences to disable the option “Open new windows in a new tab instead.” After opening the malicious link, the script waits 30 seconds and then launches our window-closing program to close the attack window. Closing any newly opened windows between each attack helps to ensure that only one attack is underway at any time, and it reduces clutter on the victim machine screen.

 1: #!/bin/bash
 2: 
 3: IFS=''
 4: FIFO_FILE=/usr/local/share/routers/fifo
 5: 
 6: while read url < $FIFO_FILE; do
 7:         firefox -remote "openurl($url)"
 8:         sleep 30
 9:         ./closer
10: done 

Figure 5. Link-clicking shell script.

Window-Closing Program

The final component of our CSRF attack victim is a program that cleans up any Firefox windows left open after our 30 seconds of attack time has expired. The main window, which keeps the user logged in to each router (Figure 2), must remain open. The resulting C program (shown in Figure 6) uses window titles to track our “KeepLoggedIn” page and closes all others.

The program uses libX11, a low-level X Window System library, to locate windows to be closed and to generate and send the event messages to close those windows. It begins by opening a connection to the X server in lines 88–93. Next, in lines 96–98, it traverses all open windows. The X Window System organizes the open windows in a tree structure beginning with the root; the recursive process of visiting each window occurs in the traverse function in lines 66–81. The actual work of examining window titles and closing appropriate windows occurs in the process_window function in lines 32–64. Windows with a class of Navigator – Firefox are checked to see if their titles contain KeepLoggedIn. If so, they are “spared,” and if not, they are “closed;” the close_window function, in lines 10–30, generates the event needed to delete (close) the selected window.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
 /* link with -lX11 */
 
 #include <X11/Xatom.h>
 #include <X11/Xlib.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
 static void close_window(Display *display, Window window)
 {
    static Atom wm_protocols = 0, wm_delete_window = 0;
    XClientMessageEvent event;

    if (wm_protocols == 0)
       wm_protocols = XInternAtom(display, "WM_PROTOCOLS", False);
    if (wm_delete_window == 0)
       wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", False);
 
    memset(&event, 0, sizeof event);
    event.type = ClientMessage;
    event.display = display;
    event.window = window;
    event.message_type = wm_protocols;
    event.format = 32;
    event.data.l[0] = wm_delete_window;
    event.data.l[1] = CurrentTime;
 
    XSendEvent(display, window, False, 0L, (XEvent *) &event);
 }

 static void process_window(Display *display, Window window)
 {
    static const char firefox_class[] = "Navigator\0Firefox";
    Atom actualType = 0;
    int actualFormat = 0;
    unsigned long nitems = 0;
    unsigned long bytes_after = 0;
    unsigned char *prop = NULL;

    XGetWindowProperty (display, window, XA_WM_CLASS, 0, 65535, 0, 
                       XA_STRING, &actualType, &actualFormat,
                       &nitems, &bytes_after, &prop);
    if (prop == NULL)
       return;
    if (memcmp(prop, firefox_class, sizeof firefox_class))
       return;
    XFree (prop);

    XGetWindowProperty (display, window, XA_WM_NAME, 0, 65535, 0,
                       XA_STRING, &actualType, &actualFormat,
                       &nitems, &bytes_after, &prop);

    if (prop == NULL)
       return;
    if (strstr ((char *) prop, "KeepLoggedIn"))
       printf ("%lx spared\n", window);
    else
    {
       printf ("%lx closed\n", window);
       close_window (display, window);
     }
    XFree (prop);
 }

 static void traverse(Display *display, Window window)
 {
    Window root = 0, parent = 0, *children = NULL;
    unsigned int nchildren, i;
   
    process_window(display, window);
 
    XQueryTree(display, window, &root, &parent, &children, &nchildren);
    
    if (children != NULL)
     {
       for (i = 0; i < nchildren; ++i)
          traverse(display, children[i]);
       XFree(children);
    }
 }

 int main (void)
 {
    Display *display;
    Window window;
 
    display = XOpenDisplay(NULL);
    if (display == NULL)
    {
       fputs("cannot open display\n", stderr);
       return EXIT_FAILURE;
    }
 
 
    window = DefaultRootWindow (display);
    traverse(display, window);   
    XCloseDisplay(display);
    return EXIT_SUCCESS;
 }

Figure 6. Window-closing program that closes any Firefox windows that do not contain “KeepLoggedIn” in the title.

Conclusion

By stitching together a short HTML document, PHP file, shell script, and C program, we designed a CSRF victim link-clicking robot with minimal effort. A logical extension to the robot would be to add support for Flash, Java, and other attack vectors; to add operating system or browser-specific attacks; or to require attackers to bypass anti-malware software of some form.

Note that these scripts and programs have not been thoroughly audited for security issues. Before modifying or using this code,we recommend that you carefully consider the security impact of accepting and handling link inputs from third parties and that you ensure that the inputs are validated and sanitized correctly before they are passed to shell scripts, Firefox, or other programs.