Tentukan apakah panggilan ajax gagal karena respons tidak aman atau koneksi ditolak

115

Saya telah melakukan banyak penelitian dan tidak dapat menemukan cara untuk mengatasinya. Saya mencoba melakukan panggilan jQuery ajax dari server https ke server https locahost yang menjalankan jetty dengan sertifikat yang ditandatangani sendiri. Masalah saya adalah saya tidak dapat menentukan apakah responsnya adalah koneksi yang ditolak atau respons yang tidak aman (karena kurangnya penerimaan sertifikat). Adakah cara untuk menentukan perbedaan antara kedua skenario? Itu responseText, dan statusCodeselalu sama di kedua kasus, meskipun di konsol chrome saya dapat melihat perbedaannya:

net::ERR_INSECURE_RESPONSE
net::ERR_CONNECTION_REFUSED

responseTextselalu "" dan statusCodeselalu "0" untuk kedua kasus.

Pertanyaan saya adalah, bagaimana saya bisa menentukan apakah panggilan ajax jQuery gagal karena ERR_INSECURE_RESPONSEatau karena ERR_CONNECTION_REFUSED?

Setelah sertifikat diterima semuanya berfungsi dengan baik, tetapi saya ingin tahu apakah server localhost dimatikan, atau aktif dan berjalan tetapi sertifikat belum diterima.

$.ajax({
    type: 'GET',
    url: "https://localhost/custom/server/",
    dataType: "json",
    async: true,
    success: function (response) {
        //do something
    },
    error: function (xhr, textStatus, errorThrown) {
        console.log(xhr, textStatus, errorThrown); //always the same for refused and insecure responses.
    }
});

masukkan deskripsi gambar di sini

Bahkan melakukan permintaan secara manual saya mendapatkan hasil yang sama:

var request = new XMLHttpRequest();
request.open('GET', "https://localhost/custom/server/", true);
request.onload = function () {
    console.log(request.responseText);
};
request.onerror = function () {
    console.log(request.responseText);
};
request.send();
taxicala
sumber
6
saya akan memposting kode saya, tetapi itu bukan kesalahan kode javascript. Mohon dibaca dengan seksama pertanyaan saya.
taxicala
1
Apakah dua argumen callback error lainnya memberikan wawasan tambahan? function (xhr, status, msg) {...Saya ragu mereka akan melakukannya, tetapi patut dicoba.
Kevin B
2
Tidak. Dari server ke server lain. Server jetty (berjalan di locahost) telah mengatur header CORS dengan benar karena setelah saya menerima sertifikat semuanya bekerja seperti yang diharapkan. Saya ingin menentukan apakah sertifikat harus diterima atau dermaga rusak.
taxicala
5
Sangat mungkin bahwa kurangnya informasi dari browser sepenuhnya disengaja. Cukup sebuah "kesalahan" akan memberikan sejumlah informasi kepada peretas yang diusulkan, misalnya. "Sistem ini memiliki sesuatu yang mendengarkan di port, dll."
Katana314
1
itu bukan sesuatu yang saya inginkan, itu adalah sesuatu yang saya butuhkan berdasarkan desain dan sifat dari apa yang saya kembangkan. sisi server bukanlah pilihan.
taxicala

Jawaban:

67

Tidak ada cara untuk membedakannya dari Browser Web terbaru.

Spesifikasi W3C:

Langkah-langkah di bawah ini menjelaskan apa yang harus dilakukan agen pengguna untuk permintaan lintas sumber sederhana :

Terapkan langkah-langkah membuat permintaan dan amati aturan permintaan di bawah ini saat membuat permintaan.

Jika tanda pengalihan manual tidak disetel dan responsnya memiliki kode status HTTP 301, 302, 303, 307, atau 308 Terapkan langkah pengalihan.

Jika pengguna akhir membatalkan permintaan Terapkan langkah-langkah membatalkan.

Jika ada kesalahan jaringan Jika terjadi kesalahan DNS, kegagalan negosiasi TLS, atau jenis kesalahan jaringan lainnya, terapkan langkah-langkah kesalahan jaringan . Jangan meminta interaksi pengguna akhir apa pun.

Catatan: Ini tidak termasuk tanggapan HTTP yang menunjukkan beberapa jenis kesalahan, seperti kode status HTTP 410.

Jika tidak, Lakukan pemeriksaan berbagi sumber daya. Jika kembali gagal, terapkan langkah-langkah kesalahan jaringan. Jika tidak, jika mengembalikan pass, hentikan algoritme ini dan setel status permintaan lintas sumber ke sukses. Jangan benar-benar menghentikan permintaan tersebut.

Seperti yang bisa Anda baca, kesalahan jaringan tidak menyertakan respons HTTP yang menyertakan kesalahan, itulah sebabnya Anda akan selalu mendapatkan 0 sebagai kode status, dan "" sebagai kesalahan.

Sumber


Catatan : Contoh berikut dibuat menggunakan Google Chrome Versi 43.0.2357.130 dan terhadap lingkungan yang saya buat untuk meniru OP satu. Kode untuk mengaturnya ada di bagian bawah jawaban.


Saya berpikir bahwa pendekatan Untuk mengatasi ini akan membuat permintaan sekunder melalui HTTP daripada HTTPS sebagai jawaban ini tetapi saya ingat itu tidak mungkin karena versi browser yang lebih baru memblokir konten campuran.

Artinya, Browser Web tidak akan mengizinkan permintaan melalui HTTP jika Anda menggunakan HTTPS dan sebaliknya.

Ini sudah seperti ini sejak beberapa tahun yang lalu tetapi versi Browser Web yang lebih lama seperti Mozilla Firefox di bawahnya versi 23 memungkinkannya.

Bukti tentang itu:

Membuat permintaan HTTP dari HTTPS menggunakan konsol Web Broser

var request = new XMLHttpRequest();
request.open('GET', "http://localhost:8001", true);
request.onload = function () {
    console.log(request.responseText);
};
request.onerror = function () {
    console.log(request.responseText);
};
request.send();

akan menghasilkan kesalahan berikut:

Konten Campuran: Halaman di ' https: // localhost: 8000 / ' dimuat melalui HTTPS, tetapi meminta titik akhir XMLHttpRequest ' http: // localhost: 8001 / ' yang tidak aman. Permintaan ini telah diblokir; konten harus disajikan melalui HTTPS.

Kesalahan yang sama akan muncul di konsol browser jika Anda mencoba melakukan ini dengan cara lain seperti menambahkan Iframe.

<iframe src="http://localhost:8001"></iframe>

Menggunakan koneksi Socket juga diposting sebagai jawaban , saya cukup yakin bahwa hasilnya akan sama / mirip tetapi saya sudah mencobanya.

Mencoba membuka koneksi soket dari Web Broswer menggunakan HTTPS ke titik akhir soket non Secure akan berakhir dengan kesalahan konten campuran.

new WebSocket("ws://localhost:8001", "protocolOne");

1) Konten Campuran: Halaman di ' https: // localhost: 8000 / ' dimuat melalui HTTPS, tetapi berusaha menyambung ke titik akhir WebSocket yang tidak aman 'ws: // localhost: 8001 /'. Permintaan ini telah diblokir; titik akhir ini harus tersedia melalui WSS.

2) DOMException yang tidak tertangkap: Gagal membuat 'WebSocket': Sambungan WebSocket yang tidak aman tidak dapat dimulai dari halaman yang dimuat melalui HTTPS.

Kemudian saya mencoba menyambung ke titik akhir wss juga, lihat Jika saya dapat membaca beberapa informasi tentang kesalahan koneksi jaringan:

var exampleSocket = new WebSocket("wss://localhost:8001", "protocolOne");
exampleSocket.onerror = function(e) {
    console.log(e);
}

Pelaksana cuplikan di atas dengan Server dimatikan menghasilkan:

Koneksi WebSocket ke 'wss: // localhost: 8001 /' gagal: Kesalahan dalam pembuatan koneksi: net :: ERR_CONNECTION_REFUSED

Menjalankan cuplikan di atas dengan Server Diaktifkan

Sambungan WebSocket ke 'wss: // localhost: 8001 /' gagal: Handshake pembuka WebSocket dibatalkan

Tetapi sekali lagi, kesalahan bahwa "fungsi onerror" yang dihasilkan ke konsol tidak memiliki tip untuk membedakan satu kesalahan dari yang lain.


Menggunakan proxy sebagai saran jawaban ini dapat bekerja tetapi hanya jika server "target" memiliki akses publik.

Ini tidak terjadi di sini, jadi mencoba menerapkan proxy dalam skenario ini akan membawa Kami ke masalah yang sama.

Kode untuk membuat server HTTPS Node.js :

Saya telah membuat dua server HTTPS Nodejs, yang menggunakan sertifikat yang ditandatangani sendiri:

targetServer.js:

var https = require('https');
var fs = require('fs');

var options = {
    key: fs.readFileSync('./certs2/key.pem'),
    cert: fs.readFileSync('./certs2/key-cert.pem')
};

https.createServer(options, function (req, res) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
    res.writeHead(200);
    res.end("hello world\n");
}).listen(8001);

applicationServer.js:

var https = require('https');
var fs = require('fs');

var options = {
    key: fs.readFileSync('./certs/key.pem'),
    cert: fs.readFileSync('./certs/key-cert.pem')
};

https.createServer(options, function (req, res) {
    res.writeHead(200);
    res.end("hello world\n");
}).listen(8000);

Untuk membuatnya bekerja, Anda harus memiliki Nodejs Terinstal, Perlu membuat sertifikat terpisah untuk setiap server dan menyimpannya di folder certs dan certs2 yang sesuai.

Untuk menjalankannya cukup jalankan node applicationServer.jsdan node targetServer.jsdi terminal (contoh ubuntu).

ecarrizo.dll
sumber
6
Ini jawaban terlengkap sejauh ini. Ini menunjukkan bahwa Anda benar-benar telah melakukan beberapa pekerjaan penelitian dan pengujian. Saya melihat Anda telah mencoba segala cara yang mungkin untuk melakukan ini dan semua skenario dijelaskan dengan sangat baik. Anda juga telah memberikan kode contoh untuk menyiapkan lingkungan pengujian dengan cepat! Kerja yang sangat bagus! Terima kasih atas wawasannya!
taxicala
Jawaban yang sangat bagus! Namun saya ingin menunjukkan bahwa sertifikat yang ditandatangani sendiri masih akan membuat kesalahan dari browser jika berasal dari server terpisah. @ecarrizo
FabricioG
Jika kita berbicara tentang kesalahan jaringan, Mungkin Anda dapat melihat kesalahan di konsol, Secara Manual. tetapi Anda tidak memiliki akses ke sana dari kode di lingkungan yang aman.
ecarrizo
32

Sampai sekarang: Tidak ada cara untuk membedakan acara ini di antara pengguna browser. Karena browser tidak menyediakan acara untuk diakses oleh pengembang. (Juli 2015)

Jawaban ini hanya berusaha memberikan ide-ide untuk solusi yang potensial, meskipun hacky dan tidak lengkap.


Penafian: jawaban ini tidak lengkap karena tidak sepenuhnya menyelesaikan masalah OP (karena kebijakan lintas sumber). Namun gagasan itu sendiri memiliki beberapa manfaat yang dikembangkan lebih lanjut oleh: @artur grzesiak di sini , menggunakan proxy dan ajax.


Setelah melakukan sedikit penelitian sendiri, tampaknya tidak ada bentuk pengecekan kesalahan apa pun untuk perbedaan antara koneksi ditolak dan respons tidak aman, setidaknya sejauh javascript memberikan respons untuk perbedaan antara keduanya.

Konsensus umum penelitian saya adalah bahwa sertifikat SSL ditangani oleh browser, jadi sampai sertifikat yang ditandatangani sendiri diterima oleh pengguna, browser mengunci semua permintaan, termasuk yang untuk kode status. Browser dapat (jika dikodekan) mengirim kembali kode statusnya sendiri untuk respons yang tidak aman, tetapi itu tidak benar-benar membantu apa pun, dan bahkan kemudian, Anda akan memiliki masalah dengan kompatibilitas browser (chrome / firefox / IE memiliki standar yang berbeda. .. sekali lagi)

Karena pertanyaan awal Anda adalah untuk memeriksa status server Anda antara aktif versus memiliki sertifikat yang tidak diterima, tidak bisakah Anda membuat permintaan HTTP standar seperti itu?

isUp = false;
isAccepted = false;

var isUpRequest = new XMLHttpRequest();
isUpRequest.open('GET', "http://localhost/custom/server/", true); //note non-ssl port
isUpRequest.onload = function() {
    isUp = true;
    var isAcceptedRequest = new XMLHttpRequest();
    isAcceptedRequest.open('GET', "https://localhost/custom/server/", true); //note ssl port
    isAcceptedRequest.onload = function() {
        console.log("Server is up and certificate accepted");
        isAccepted = true;
    }
    isAcceptedRequest.onerror = function() {
        console.log("Server is up and certificate is not accepted");
    }
    isAcceptedRequest.send();
};
isUpRequest.onerror = function() {
    console.log("Server is down");
};
isUpRequest.send();

Memang ini membutuhkan permintaan tambahan untuk memverifikasi konektivitas server, tetapi itu harus menyelesaikan pekerjaan dengan proses eliminasi. Masih terasa hacky, dan saya bukan penggemar permintaan berlipat ganda.

acupofjose
sumber
7
Saya telah membaca seluruh jawaban dan menurut saya ini tidak akan berhasil. Kedua server saya menjalankan HTTPS, artinya pada saat saya mengaktifkan permintaan ke server HTTP, browser akan langsung membatalkannya, karena Anda tidak dapat membuat permintaan ajax dari server HTTPS ke server HTTP
taxicala
11

@ Jawaban Schultzie cukup dekat, tetapi jelas http- secara umum - tidak akan berfungsi dari httpsdalam lingkungan browser.

Apa yang dapat Anda lakukan adalah menggunakan server perantara (proxy) untuk membuat permintaan atas nama Anda. Proksi harus memungkinkan untuk meneruskan httppermintaan dari httpsasal atau untuk memuat konten dari sumber yang ditandatangani sendiri .

Memiliki server Anda sendiri dengan sertifikat yang tepat mungkin berlebihan dalam kasus Anda - karena Anda dapat menggunakan setelan ini alih-alih mesin dengan sertifikat yang ditandatangani sendiri - tetapi ada banyak layanan proxy terbuka anonim di luar sana.

Jadi dua pendekatan yang muncul di benak saya adalah:

  1. ajax request - dalam kasus seperti itu proxy harus menggunakan pengaturan CORS yang sesuai
  2. penggunaan iframe - Anda memuat skrip Anda (mungkin terbungkus html) di dalam iframe melalui proxy. Setelah skrip dimuat, ia mengirim pesan ke .parentWindow. Jika jendela Anda menerima pesan, Anda dapat yakin bahwa server sedang berjalan (atau lebih tepatnya berjalan sepersekian detik sebelumnya).

Jika Anda hanya tertarik dengan lingkungan lokal Anda, Anda dapat mencoba menjalankan chrome dengan --disable-web-securitybendera.


Saran lain: apakah Anda mencoba memuat gambar secara terprogram untuk mengetahui apakah ada lebih banyak info di sana?

artur grzesiak
sumber
1

Lihat jQuery.ajaxError () Diambil referensi dari: Penanganan Kesalahan AJAX jQuery (Kode Status HTTP) Ini menangkap kesalahan Ajax global yang dapat Anda tangani dengan berbagai cara melalui HTTP atau HTTPS:

if (jqXHR.status == 501) {
//insecure response
} else if (jqXHR.status == 102) {
//connection refused
}
Varshaan
sumber
Ini sama dengan melakukan ajax seperti yang saya lakukan, tetapi memusatkan respons kesalahan di satu tempat.
taxicala
1
Intinya adalah bahwa sertifikat tidak dimuat saat panggilan ajax dilakukan karena tampaknya masalahnya. cara apapun gl dengan menemukan jawaban :)
Varshaan
2
Tidak, masalahnya adalah saya ingin menentukan perbedaan antara harus menerima sertifikat, dan mengetahui apakah server dimatikan.
taxicala
1
Baik ketika koneksi ditolak dan sertifikat tidak dipercaya, saya mendapatkan jqXHR.status = 0 yang sama (dan jqXHR.readyState = 0), bukan qXHR.status == 102 atau 501 seperti yang dijelaskan dalam jawaban ini.
anre
1

Sayangnya, API XHR browser saat ini tidak memberikan indikasi eksplisit kapan browser menolak untuk terhubung karena "respons tidak aman", dan juga saat browser tidak mempercayai sertifikat HTTP / SSL situs web.

Tetapi ada cara untuk mengatasi masalah ini.

Salah satu solusi yang saya buat untuk menentukan kapan browser tidak mempercayai sertifikat HTTP / SSL, adalah pertama-tama mendeteksi jika kesalahan XHR telah terjadi (misalnya menggunakan error()panggilan balik jQuery ), kemudian periksa apakah panggilan XHR ke 'https : // 'URL, lalu periksa apakah XHRreadyState nya 0, yang berarti koneksi XHR belum dibuka (yang terjadi jika browser tidak menyukai sertifikat).

Inilah kode tempat saya melakukan ini: https://github.com/maratbn/RainbowPayPress/blob/e9e9472a36ced747a0f9e5ca9fa7d96959aeaf8a/rainbowpaypress/js/le_requirejs/public/model_info__transaction_details.js#L88

maratbn
sumber
1

Saya rasa saat ini tidak ada cara untuk mendeteksi pesan kesalahan ini, tetapi peretasan yang dapat Anda lakukan adalah menggunakan server seperti nginx di depan server aplikasi Anda, sehingga jika server aplikasi mati Anda akan mendapatkan yang buruk. kesalahan gateway dari nginx dengan 502kode status yang dapat Anda deteksi di JS. Jika tidak, jika sertifikat tidak valid, Anda masih akan mendapatkan kesalahan umum yang sama dengan statusCode = 0.

gafi
sumber