Расширение Firefox: отменяйте запросы и отправляйте поддельные ответы

Я пытаюсь разработать расширение Firefox, которое отбрасывает каждый HTTP-запрос на определенный сайт и возвращает поддельный ответ. Ни один запрос не должен проходить к исходному веб-серверу, но я хочу иметь возможность создать собственный ответ. Я попытался перехватить сообщение «http-on-modify-request», но отмена запроса, похоже, не работает, так как впоследствии я не могу смоделировать реальный ответ. Точно так же, используя экземпляр nsITraceableStream, я не могу действительно отменить запрос. У меня нет идей, может кто-нибудь помочь?


person Niklas B.    schedule 28.08.2011    source источник
comment
Для чего предполагается использовать это расширение?   -  person Amir Raminfar    schedule 28.08.2011
comment
Возможно, вы хотите посмотреть, как LeechBlock блокирует запросы: addons.mozilla. org/en-US/firefox/addon/leechblock   -  person Felix Kling    schedule 28.08.2011
comment
@Felix: я знаю, как блокировать запросы, но если я сделаю это обычным способом (реагируя на сообщение http-on-modify-request), я не смогу подделать ответ.   -  person Niklas B.    schedule 28.08.2011
comment
@Amir: Мы (компания по ИТ-безопасности, в которой я работаю) хотим использовать его в демонстрационных целях, чтобы показать, как легко манипулировать защищенными SSL-соединениями, когда у вас есть доступ к клиентской стороне (браузеру).   -  person Niklas B.    schedule 28.08.2011
comment
@Niklas: Почему бы просто не использовать для этого автоответчик Fiddler (www.fiddler2.com)? Затем вы можете сделать свою демонстрацию во ВСЕХ браузерах.   -  person EricLaw    schedule 29.08.2011
comment
@EricLaw: мне трудно представить, что Fiddler может манипулировать ответами HTTPS, по крайней мере, не вызывая предупреждения о сертификате. Изменить: справа: http://www.fiddler2.com/fiddler/help/httpsdecryption.asp   -  person Wladimir Palant    schedule 29.08.2011
comment
@EricLaw: Мы делаем это прямо сейчас, используя прокси и доверенную компанию CA в браузере. Но еще лучше, если исходный EV-trust отображается в адресной строке.   -  person Niklas B.    schedule 29.08.2011


Ответы (1)


Ответ ниже был заменен с Firefox 21, теперь метод nsIHttpChannel.redirectTo() прекрасно справляется со своей задачей. Вы можете перенаправить на data: URI, что-то вроде этого будет работать:

Components.utils.import("resource://gre/modules/Services.jsm");
const Ci = Components.interfaces;

[...]

onModifyRequest: function(channel)
{
  if (channel instanceof Ci.nsIHttpChannel && shouldRedirect(channel.URI.spec))
  {
    let redirectURL = "data:text/html," + encodeURIComponent("<html>Hi there!</html>");
    channel.redirectTo(Services.io.newURI(redirectURI, null, null));
  }
}

Исходный ответ (устаревший)

С каждым каналом связан прослушиватель потока, который получает уведомление при получении данных. Все, что вам нужно сделать, чтобы подделать ответ, — это получить этот слушатель и передать ему неправильные данные. И nsITraceableChannel на самом деле является способом сделать это. Вам нужно заменить обычный слушатель канала на свой, который ничего не сделает, после этого вы можете закрыть канал, не уведомляя об этом слушателя. Затем вы запускаете прослушиватель и передаете ему свои данные. Что-то вроде этого:

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
const Cc = Components.classes;
const Ci = Components.interfaces;

[...]

onModifyRequest: function(channel)
{
  if (channel instanceof Ci.nsIHttpChannel && channel instanceof Ci.nsITraceableChannel)
  {
    // Our own listener for the channel
    var fakeListener = {
      QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener,
                        Ci.nsIRequestObserver, Ci.nsIRunnable]),
      oldListener: null,
      run: function()
      {
        // Replace old listener by our fake listener
        this.oldListener = channel.setNewListener(this);

        // Now we can cancel the channel, listener old won't notice
        //channel.cancel(Components.results.NS_BINDING_ABORTED);
      },
      onDataAvailable: function(){},
      onStartRequest: function(){},
      onStopRequest: function(request, context, status)
      {
        // Call old listener with our data and set "response" headers
        var stream = Cc["@mozilla.org/io/string-input-stream;1"]
                       .createInstance(Ci.nsIStringInputStream);
        stream.setData("<html>Hi there!</html>", -1);
        this.oldListener.onStartRequest(channel, context);
        channel.setResponseHeader("Refresh", "5; url=http://google.com/", false);
        this.oldListener.onDataAvailable(channel, context, stream, 0, stream.available());
        this.oldListener.onStopRequest(channel, context, Components.results.NS_OK);
      }
    }

    // We cannot replace the listener right now, see
    // https://bugzilla.mozilla.org/show_bug.cgi?id=646370.
    // Do it asynchronously instead.
    var threadManager = Cc["@mozilla.org/thread-manager;1"]
                          .getService(Ci.nsIThreadManager);
    threadManager.currentThread.dispatch(fakeListener, Ci.nsIEventTarget.DISPATCH_NORMAL);
  }
}

Проблема с этим кодом по-прежнему заключается в том, что страница отображается пустой, если канал отменен (поэтому я прокомментировал эту строку) - кажется, что слушатель все еще смотрит на канал и замечает, что он отменен.

person Wladimir Palant    schedule 29.08.2011
comment
Владимир, спасибо, посмотрю! - person Niklas B.; 29.08.2011
comment
Привет, Владимир, я попробовал ваш код в Firefox 6, и FiFo segfaults на oldListener.onStartRequest(channel, oldContext); Я попробую FiFo 3.5 сейчас, чтобы быть уверенным. На самом деле я использовал очень похожий подход с нулевым прослушивателем, но в onExamineResponse, где я, похоже, не смог отменить запрос и установить какие-либо заголовки ответа. - person Niklas B.; 29.08.2011
comment
@Niklas: Вероятно, это потому, что oldContext не был установлен в этот момент, поэтому вы вызываете его с неправильным контекстом - я предполагаю, что канал отменяется асинхронно, что означает, что onStopRequest вызывается слишком поздно. Перемещение всего кода, имитирующего запрос (onStartRequest и Ко.), в onStopRequest вашего поддельного слушателя, должно исправить это. - person Wladimir Palant; 29.08.2011
comment
У меня уже была эта мысль, и я попробовал этот код, но ни одно из оповещений в fakeListener никогда не вызывается. Однако вызывается alert("Registering fake listener..."). Редактировать: поддельный прослушиватель даже не вызывается, когда я не отменяю запрос. - person Niklas B.; 29.08.2011
comment
@Niklas: Вызов alert() в сетевом коде - плохая идея и может не работать по какой-то причине (он создает модальный диалог). Вы должны использовать Components.utils.reportError() или window.dump(), которые дают более надежные результаты. - person Wladimir Palant; 29.08.2011
comment
Ладно, я этого не знал. Теперь я использую Components.utils.reportError() с теми же результатами. Кажется, что setNewListener вообще не будет иметь никакого эффекта... возможно ли, что это работает только в ответ на http-on-examine-response? Весь пример кода, который я видел до сих пор, включающий setNewListener, на самом деле вызывает его в onExamineResponse. - person Niklas B.; 29.08.2011
comment
@Niklas: Забыл об этом: ошибка 646370 :-( - person Wladimir Palant; 29.08.2011
comment
очень плохо :( может быть, у вас есть идея, как я могу манипулировать заголовками ответа в http-on-examine-response? Подход, который у меня был до вашего ответа, заключался в том, чтобы изменить запрос на метод TRACE, который не влияет на серверную сторону Затем я могу манипулировать телом ответа в onExmineResponse, но кажется, что я не могу вызвать setResponseHeader (выдает NS_ERROR_ILLEGAL_VALUE) - person Niklas B.; 29.08.2011
comment
@Niklas: я максимально исправил код, см. отредактированную версию моего поста. Это работает для меня, по крайней мере, до тех пор, пока я не отменяю исходный запрос и не пытаюсь установить заголовки ответа. - person Wladimir Palant; 29.08.2011
comment
так что нет возможности установить заголовки ответов? Это важно для меня. И мне не просто нужно установить channel.contentType. - person Niklas B.; 29.08.2011
comment
@Niklas: еще раз посмотрите исправленную версию поста. Он отклонит только заголовок Content-Type, вероятно, потому, что на данный момент он уже есть в канале. Однако можно установить и другие заголовки. - person Wladimir Palant; 29.08.2011
comment
извините за это длинное обсуждение ... То, что вы говорите, правильно, вызов setResponseHeader, например, с "Set-Cookie" или "Refresh" не вызывает исключения, но Firefox, похоже, не учитывает заголовки, установленные таким образом. Он не обновляется и не устанавливает куки, по крайней мере, для меня. Если это для вас, можете ли вы сказать мне, какую версию Firefox вы используете? Я использую 6.0 на Linux x86_64. Редактировать: Я также очень хочу поблагодарить вас за время, которое вы потратили на эту проблему! - person Niklas B.; 29.08.2011
comment
@Niklas: я действительно пробовал Refresh в Firefox 9.0a1, он работает (однако эта версия Firefox блокирует перенаправление). Возможно, вы захотите изменить третий параметр с setResponseHeader (aMerge) на true, это определенно необходимо, если присутствуют другие заголовки файлов cookie. - person Wladimir Palant; 29.08.2011
comment
Это оказывается очень сложной проблемой. Я скачал 9.0a1, но ни Refresh (используя ваш литеральный код), ни Set-Cookie не работают. Я также не знаю, невидима ли упомянутая вами блокировка? Если да, как я могу убедиться, что обновление происходит, но заблокировано? - person Niklas B.; 29.08.2011
comment
@Niklas: блокировка приводит к появлению панели уведомлений. К сожалению, у меня больше нет времени проверять, работают ли у меня заголовки Set-Cookie (или попробовать другие настройки этого кода). - person Wladimir Palant; 29.08.2011
comment
Я полностью понимаю. У меня все больше и больше складывается впечатление, что подход с расширением Javascript — это тупик. Может быть, мне лучше взломать исходный код CPP, чтобы получить пользовательскую сборку Firefox, которая интегрирует мой код перенаправления или, по крайней мере, имеет 646370 исправлено. Большое спасибо за вашу отличную помощь. - person Niklas B.; 29.08.2011
comment
@Niklas: Возможно, вы захотите попробовать установить заголовки раньше, может быть, в onStartRequest. Что касается ошибки 646370 - я не думаю, что это проблема. , обходной путь работает просто отлично. - person Wladimir Palant; 29.08.2011
comment
На самом деле я был просто слишком глуп, чтобы правильно называть переменные. Параметр onStopRequest называется request, но я назвал channel.setResponseHeader. Может быть, вы можете обновить свой код, чтобы другие пользователи не запутались :) РЕДАКТИРОВАТЬ: Однако я не уверен, почему это не тот же экземпляр. Ну, по крайней мере, теперь это работает. - person Niklas B.; 29.08.2011
comment
@Niklas: Учитывая, что onStopRequest() — это замыкание, определенное внутри функции onModifyRequest(), совершенно нормально использовать переменную channel из внешней функции (она даже требуется для метода run()). Но может у вас другая установка. - person Wladimir Palant; 29.08.2011
comment
Да, ты прав. Проблема заключалась в том, что мне нужно было установить заголовки в onStartRequest. - person Niklas B.; 29.08.2011
comment
@Niklas: В вашем случае, вероятно, было перенаправление, поэтому канал в onStopRequest не был таким же, как тот, к которому вы привязали своего слушателя. Я должен отредактировать свой код, чтобы учесть этот случай. - person Wladimir Palant; 29.08.2011