tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_duplicate_headers.js (17428B)


      1 /*
      2 * Tests bugs 597706, 655389: prevent duplicate headers with differing values
      3 * for some headers like Content-Length, Location, etc.
      4 */
      5 
      6 ////////////////////////////////////////////////////////////////////////////////
      7 // Test infrastructure
      8 
      9 "use strict";
     10 
     11 // The tests in this file use number indexes to run, which can't be detected
     12 // via ESLint.
     13 /* eslint-disable no-unused-vars */
     14 
     15 const { HttpServer } = ChromeUtils.importESModule(
     16  "resource://testing-common/httpd.sys.mjs"
     17 );
     18 
     19 ChromeUtils.defineLazyGetter(this, "URL", function () {
     20  return "http://localhost:" + httpserver.identity.primaryPort;
     21 });
     22 
     23 var httpserver = new HttpServer();
     24 var test_flags = [];
     25 var testPathBase = "/dupe_hdrs";
     26 
     27 function run_test() {
     28  httpserver.start(-1);
     29 
     30  do_test_pending();
     31  run_test_number(1);
     32 }
     33 
     34 function run_test_number(num) {
     35  let testPath = testPathBase + num;
     36  httpserver.registerPathHandler(testPath, globalThis["handler" + num]);
     37 
     38  var channel = setupChannel(testPath);
     39  let flags = test_flags[num]; // OK if flags undefined for test
     40  channel.asyncOpen(
     41    new ChannelListener(globalThis["completeTest" + num], channel, flags)
     42  );
     43 }
     44 
     45 function setupChannel(url) {
     46  var chan = NetUtil.newChannel({
     47    uri: URL + url,
     48    loadUsingSystemPrincipal: true,
     49  });
     50  var httpChan = chan.QueryInterface(Ci.nsIHttpChannel);
     51  return httpChan;
     52 }
     53 
     54 function endTests() {
     55  httpserver.stop(do_test_finished);
     56 }
     57 
     58 ////////////////////////////////////////////////////////////////////////////////
     59 // Test 1: FAIL because of conflicting Content-Length headers
     60 test_flags[1] = CL_EXPECT_FAILURE;
     61 
     62 function handler1(metadata, response) {
     63  var body = "012345678901234567890123456789";
     64  // Comrades!  We must seize power from the petty-bourgeois running dogs of
     65  // httpd.js in order to reply with multiple instances of the same header!
     66  response.seizePower();
     67  response.write("HTTP/1.0 200 OK\r\n");
     68  response.write("Content-Type: text/plain\r\n");
     69  response.write("Content-Length: 30\r\n");
     70  response.write("Content-Length: 20\r\n");
     71  response.write("\r\n");
     72  response.write(body);
     73  response.finish();
     74 }
     75 
     76 function completeTest1(request) {
     77  Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
     78 
     79  run_test_number(2);
     80 }
     81 
     82 ////////////////////////////////////////////////////////////////////////////////
     83 // Test 2: OK to have duplicate same Content-Length headers
     84 
     85 function handler2(metadata, response) {
     86  var body = "012345678901234567890123456789";
     87  response.seizePower();
     88  response.write("HTTP/1.0 200 OK\r\n");
     89  response.write("Content-Type: text/plain\r\n");
     90  response.write("Content-Length: 30\r\n");
     91  response.write("Content-Length: 30\r\n");
     92  response.write("\r\n");
     93  response.write(body);
     94  response.finish();
     95 }
     96 
     97 function completeTest2(request) {
     98  Assert.equal(request.status, 0);
     99  run_test_number(3);
    100 }
    101 
    102 ////////////////////////////////////////////////////////////////////////////////
    103 // Test 3: FAIL: 2nd Content-length is blank
    104 test_flags[3] = CL_EXPECT_FAILURE;
    105 
    106 function handler3(metadata, response) {
    107  var body = "012345678901234567890123456789";
    108  response.seizePower();
    109  response.write("HTTP/1.0 200 OK\r\n");
    110  response.write("Content-Type: text/plain\r\n");
    111  response.write("Content-Length: 30\r\n");
    112  response.write("Content-Length:\r\n");
    113  response.write("\r\n");
    114  response.write(body);
    115  response.finish();
    116 }
    117 
    118 function completeTest3(request) {
    119  Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
    120 
    121  run_test_number(4);
    122 }
    123 
    124 ////////////////////////////////////////////////////////////////////////////////
    125 // Test 4: ensure that blank C-len header doesn't allow attacker to reset Clen,
    126 // then insert CRLF attack
    127 test_flags[4] = CL_EXPECT_FAILURE;
    128 
    129 function handler4(metadata, response) {
    130  var body = "012345678901234567890123456789";
    131 
    132  response.seizePower();
    133  response.write("HTTP/1.0 200 OK\r\n");
    134  response.write("Content-Type: text/plain\r\n");
    135  response.write("Content-Length: 30\r\n");
    136 
    137  // Bad Mr Hacker!  Bad!
    138  var evilBody = "We are the Evil bytes, Evil bytes, Evil bytes!";
    139  response.write("Content-Length:\r\n");
    140  response.write("Content-Length: %s\r\n\r\n%s" % (evilBody.length, evilBody));
    141  response.write("\r\n");
    142  response.write(body);
    143  response.finish();
    144 }
    145 
    146 function completeTest4(request) {
    147  Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
    148 
    149  run_test_number(5);
    150 }
    151 
    152 ////////////////////////////////////////////////////////////////////////////////
    153 // Test 5: ensure that we take 1st instance of duplicate, nonmerged headers that
    154 // are permitted : (ex: Referrer)
    155 
    156 function handler5(metadata, response) {
    157  var body = "012345678901234567890123456789";
    158  response.seizePower();
    159  response.write("HTTP/1.0 200 OK\r\n");
    160  response.write("Content-Type: text/plain\r\n");
    161  response.write("Content-Length: 30\r\n");
    162  response.write("Referer: naive.org\r\n");
    163  response.write("Referer: evil.net\r\n");
    164  response.write("\r\n");
    165  response.write(body);
    166  response.finish();
    167 }
    168 
    169 function completeTest5(request) {
    170  try {
    171    let referer = request.getResponseHeader("Referer");
    172    Assert.equal(referer, "naive.org");
    173  } catch (ex) {
    174    do_throw("Referer header should be present");
    175  }
    176 
    177  run_test_number(6);
    178 }
    179 
    180 ////////////////////////////////////////////////////////////////////////////////
    181 // Test 5: FAIL if multiple, different Location: headers present
    182 // - needed to prevent CRLF injection attacks
    183 test_flags[6] = CL_EXPECT_FAILURE;
    184 
    185 function handler6(metadata, response) {
    186  var body = "012345678901234567890123456789";
    187  response.seizePower();
    188  response.write("HTTP/1.0 301 Moved\r\n");
    189  response.write("Content-Type: text/plain\r\n");
    190  response.write("Content-Length: 30\r\n");
    191  response.write("Location: " + URL + "/content\r\n");
    192  response.write("Location: http://www.microsoft.com/\r\n");
    193  response.write("Connection: close\r\n");
    194  response.write("\r\n");
    195  response.write(body);
    196  response.finish();
    197 }
    198 
    199 function completeTest6(request) {
    200  Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
    201 
    202  //  run_test_number(7);   // Test 7 leaking under e10s: unrelated bug?
    203  run_test_number(8);
    204 }
    205 
    206 ////////////////////////////////////////////////////////////////////////////////
    207 // Test 7: OK to have multiple Location: headers with same value
    208 
    209 function handler7(metadata, response) {
    210  var body = "012345678901234567890123456789";
    211  response.seizePower();
    212  response.write("HTTP/1.0 301 Moved\r\n");
    213  response.write("Content-Type: text/plain\r\n");
    214  response.write("Content-Length: 30\r\n");
    215  // redirect to previous test handler that completes OK: test 5
    216  response.write("Location: " + URL + testPathBase + "5\r\n");
    217  response.write("Location: " + URL + testPathBase + "5\r\n");
    218  response.write("Connection: close\r\n");
    219  response.write("\r\n");
    220  response.write(body);
    221  response.finish();
    222 }
    223 
    224 function completeTest7(request) {
    225  // for some reason need this here
    226  request.QueryInterface(Ci.nsIHttpChannel);
    227 
    228  try {
    229    let referer = request.getResponseHeader("Referer");
    230    Assert.equal(referer, "naive.org");
    231  } catch (ex) {
    232    do_throw("Referer header should be present");
    233  }
    234 
    235  run_test_number(8);
    236 }
    237 
    238 ////////////////////////////////////////////////////////////////////////////////
    239 // FAIL if 2nd Location: headers blank
    240 test_flags[8] = CL_EXPECT_FAILURE;
    241 
    242 function handler8(metadata, response) {
    243  var body = "012345678901234567890123456789";
    244  response.seizePower();
    245  response.write("HTTP/1.0 301 Moved\r\n");
    246  response.write("Content-Type: text/plain\r\n");
    247  response.write("Content-Length: 30\r\n");
    248  // redirect to previous test handler that completes OK: test 4
    249  response.write("Location: " + URL + testPathBase + "4\r\n");
    250  response.write("Location:\r\n");
    251  response.write("Connection: close\r\n");
    252  response.write("\r\n");
    253  response.write(body);
    254  response.finish();
    255 }
    256 
    257 function completeTest8(request) {
    258  Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
    259 
    260  run_test_number(9);
    261 }
    262 
    263 ////////////////////////////////////////////////////////////////////////////////
    264 // Test 9: ensure that blank Location header doesn't allow attacker to reset,
    265 // then insert an evil one
    266 test_flags[9] = CL_EXPECT_FAILURE;
    267 
    268 function handler9(metadata, response) {
    269  var body = "012345678901234567890123456789";
    270  response.seizePower();
    271  response.write("HTTP/1.0 301 Moved\r\n");
    272  response.write("Content-Type: text/plain\r\n");
    273  response.write("Content-Length: 30\r\n");
    274  // redirect to previous test handler that completes OK: test 2
    275  response.write("Location: " + URL + testPathBase + "2\r\n");
    276  response.write("Location:\r\n");
    277  // redirect to previous test handler that completes OK: test 4
    278  response.write("Location: " + URL + testPathBase + "4\r\n");
    279  response.write("Connection: close\r\n");
    280  response.write("\r\n");
    281  response.write(body);
    282  response.finish();
    283 }
    284 
    285 function completeTest9(request) {
    286  // All redirection should fail:
    287  Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
    288 
    289  run_test_number(10);
    290 }
    291 
    292 ////////////////////////////////////////////////////////////////////////////////
    293 // Test 10: FAIL:  if conflicting values for Content-Dispo
    294 test_flags[10] = CL_EXPECT_FAILURE;
    295 
    296 function handler10(metadata, response) {
    297  var body = "012345678901234567890123456789";
    298  response.seizePower();
    299  response.write("HTTP/1.0 200 OK\r\n");
    300  response.write("Content-Type: text/plain\r\n");
    301  response.write("Content-Length: 30\r\n");
    302  response.write("Content-Disposition: attachment; filename=foo\r\n");
    303  response.write("Content-Disposition: attachment; filename=bar\r\n");
    304  response.write("Content-Disposition: attachment; filename=baz\r\n");
    305  response.write("\r\n");
    306  response.write(body);
    307  response.finish();
    308 }
    309 
    310 function completeTest10(request) {
    311  Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
    312 
    313  run_test_number(11);
    314 }
    315 
    316 ////////////////////////////////////////////////////////////////////////////////
    317 // Test 11: OK to have duplicate same Content-Disposition headers
    318 
    319 function handler11(metadata, response) {
    320  var body = "012345678901234567890123456789";
    321  response.seizePower();
    322  response.write("HTTP/1.0 200 OK\r\n");
    323  response.write("Content-Type: text/plain\r\n");
    324  response.write("Content-Length: 30\r\n");
    325  response.write("Content-Disposition: attachment; filename=foo\r\n");
    326  response.write("Content-Disposition: attachment; filename=foo\r\n");
    327  response.write("\r\n");
    328  response.write(body);
    329  response.finish();
    330 }
    331 
    332 function completeTest11(request) {
    333  Assert.equal(request.status, 0);
    334 
    335  try {
    336    var chan = request.QueryInterface(Ci.nsIChannel);
    337    Assert.equal(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
    338    Assert.equal(chan.contentDispositionFilename, "foo");
    339    Assert.equal(chan.contentDispositionHeader, "attachment; filename=foo");
    340  } catch (ex) {
    341    do_throw("error parsing Content-Disposition: " + ex);
    342  }
    343 
    344  run_test_number(12);
    345 }
    346 
    347 ////////////////////////////////////////////////////////////////////////////////
    348 // Bug 716801 OK for Location: header to be blank
    349 
    350 function handler12(metadata, response) {
    351  var body = "012345678901234567890123456789";
    352  response.seizePower();
    353  response.write("HTTP/1.0 200 OK\r\n");
    354  response.write("Content-Type: text/plain\r\n");
    355  response.write("Content-Length: 30\r\n");
    356  response.write("Location:\r\n");
    357  response.write("Connection: close\r\n");
    358  response.write("\r\n");
    359  response.write(body);
    360  response.finish();
    361 }
    362 
    363 function completeTest12(request, data) {
    364  Assert.equal(request.status, Cr.NS_OK);
    365  Assert.equal(30, data.length);
    366 
    367  run_test_number(13);
    368 }
    369 
    370 ////////////////////////////////////////////////////////////////////////////////
    371 // Negative content length is ok
    372 test_flags[13] = CL_ALLOW_UNKNOWN_CL;
    373 
    374 function handler13(metadata, response) {
    375  var body = "012345678901234567890123456789";
    376  response.seizePower();
    377  response.write("HTTP/1.0 200 OK\r\n");
    378  response.write("Content-Type: text/plain\r\n");
    379  response.write("Content-Length: -1\r\n");
    380  response.write("Connection: close\r\n");
    381  response.write("\r\n");
    382  response.write(body);
    383  response.finish();
    384 }
    385 
    386 function completeTest13(request, data) {
    387  Assert.equal(request.status, Cr.NS_OK);
    388  Assert.equal(30, data.length);
    389 
    390  run_test_number(14);
    391 }
    392 
    393 ////////////////////////////////////////////////////////////////////////////////
    394 // leading negative content length is not ok if paired with positive one
    395 
    396 test_flags[14] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL;
    397 
    398 function handler14(metadata, response) {
    399  var body = "012345678901234567890123456789";
    400  response.seizePower();
    401  response.write("HTTP/1.0 200 OK\r\n");
    402  response.write("Content-Type: text/plain\r\n");
    403  response.write("Content-Length: -1\r\n");
    404  response.write("Content-Length: 30\r\n");
    405  response.write("Connection: close\r\n");
    406  response.write("\r\n");
    407  response.write(body);
    408  response.finish();
    409 }
    410 
    411 function completeTest14(request) {
    412  Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
    413 
    414  run_test_number(15);
    415 }
    416 
    417 ////////////////////////////////////////////////////////////////////////////////
    418 // trailing negative content length is not ok if paired with positive one
    419 
    420 test_flags[15] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL;
    421 
    422 function handler15(metadata, response) {
    423  var body = "012345678901234567890123456789";
    424  response.seizePower();
    425  response.write("HTTP/1.0 200 OK\r\n");
    426  response.write("Content-Type: text/plain\r\n");
    427  response.write("Content-Length: 30\r\n");
    428  response.write("Content-Length: -1\r\n");
    429  response.write("Connection: close\r\n");
    430  response.write("\r\n");
    431  response.write(body);
    432  response.finish();
    433 }
    434 
    435 function completeTest15(request) {
    436  Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
    437 
    438  run_test_number(16);
    439 }
    440 
    441 ////////////////////////////////////////////////////////////////////////////////
    442 // empty content length is ok
    443 test_flags[16] = CL_ALLOW_UNKNOWN_CL;
    444 let reran16 = false;
    445 
    446 function handler16(metadata, response) {
    447  var body = "012345678901234567890123456789";
    448  response.seizePower();
    449  response.write("HTTP/1.0 200 OK\r\n");
    450  response.write("Content-Type: text/plain\r\n");
    451  response.write("Content-Length: \r\n");
    452  response.write("Cache-Control: max-age=600\r\n");
    453  response.write("Connection: close\r\n");
    454  response.write("\r\n");
    455  response.write(body);
    456  response.finish();
    457 }
    458 
    459 function completeTest16(request, data) {
    460  Assert.equal(request.status, Cr.NS_OK);
    461  Assert.equal(30, data.length);
    462 
    463  if (!reran16) {
    464    reran16 = true;
    465    run_test_number(16);
    466  } else {
    467    run_test_number(17);
    468  }
    469 }
    470 
    471 ////////////////////////////////////////////////////////////////////////////////
    472 // empty content length paired with non empty is not ok
    473 test_flags[17] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL;
    474 
    475 function handler17(metadata, response) {
    476  var body = "012345678901234567890123456789";
    477  response.seizePower();
    478  response.write("HTTP/1.0 200 OK\r\n");
    479  response.write("Content-Type: text/plain\r\n");
    480  response.write("Content-Length: \r\n");
    481  response.write("Content-Length: 30\r\n");
    482  response.write("Connection: close\r\n");
    483  response.write("\r\n");
    484  response.write(body);
    485  response.finish();
    486 }
    487 
    488 function completeTest17(request) {
    489  Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
    490 
    491  run_test_number(18);
    492 }
    493 
    494 ////////////////////////////////////////////////////////////////////////////////
    495 // alpha content-length is just like -1
    496 test_flags[18] = CL_ALLOW_UNKNOWN_CL;
    497 
    498 function handler18(metadata, response) {
    499  var body = "012345678901234567890123456789";
    500  response.seizePower();
    501  response.write("HTTP/1.0 200 OK\r\n");
    502  response.write("Content-Type: text/plain\r\n");
    503  response.write("Content-Length: seventeen\r\n");
    504  response.write("Connection: close\r\n");
    505  response.write("\r\n");
    506  response.write(body);
    507  response.finish();
    508 }
    509 
    510 function completeTest18(request, data) {
    511  Assert.equal(request.status, Cr.NS_OK);
    512  Assert.equal(30, data.length);
    513 
    514  run_test_number(19);
    515 }
    516 
    517 ////////////////////////////////////////////////////////////////////////////////
    518 // semi-colons are ok too in the content-length
    519 test_flags[19] = CL_ALLOW_UNKNOWN_CL;
    520 
    521 function handler19(metadata, response) {
    522  var body = "012345678901234567890123456789";
    523  response.seizePower();
    524  response.write("HTTP/1.0 200 OK\r\n");
    525  response.write("Content-Type: text/plain\r\n");
    526  response.write("Content-Length: 30;\r\n");
    527  response.write("Connection: close\r\n");
    528  response.write("\r\n");
    529  response.write(body);
    530  response.finish();
    531 }
    532 
    533 function completeTest19(request, data) {
    534  Assert.equal(request.status, Cr.NS_OK);
    535  Assert.equal(30, data.length);
    536 
    537  run_test_number(20);
    538 }
    539 
    540 ////////////////////////////////////////////////////////////////////////////////
    541 // FAIL if 1st Location: header is blank, followed by non-blank
    542 test_flags[20] = CL_EXPECT_FAILURE;
    543 
    544 function handler20(metadata, response) {
    545  var body = "012345678901234567890123456789";
    546  response.seizePower();
    547  response.write("HTTP/1.0 301 Moved\r\n");
    548  response.write("Content-Type: text/plain\r\n");
    549  response.write("Content-Length: 30\r\n");
    550  // redirect to previous test handler that completes OK: test 4
    551  response.write("Location:\r\n");
    552  response.write("Location: " + URL + testPathBase + "4\r\n");
    553  response.write("Connection: close\r\n");
    554  response.write("\r\n");
    555  response.write(body);
    556  response.finish();
    557 }
    558 
    559 function completeTest20(request) {
    560  Assert.equal(request.status, Cr.NS_ERROR_CORRUPTED_CONTENT);
    561 
    562  endTests();
    563 }