Để chống DDOS hay anti-bot, CloudFlare sử dụng 4 phương pháp: chặn IP, Captcha, JS Challenge và redirect URL. Ở bài viết này, tôi sẽ giới thiệu cách vượt qua JS Challenge của CloudFlare sử dụng PHP code.
Chú ý: bạn cần có kiến thức về HTML, JS và lập trình nói chung trước khi đọc bài viết này!
Một site ví dụ sử dụng CloudFlare và JS Challenge để anti-bot là trang novelplanet.com. Nếu bạn vào lần đầu tiên sẽ thấy, nội dung chính không hiển thị mà sẽ là một trang loading, bắt bạn chờ 5s trước khi trả lại nội dung. Nếu tiếp tục vào lại thì bạn sẽ không phải chờ nữa mà sẽ trả lại nội dung chính luôn.
Cơ chế hoạt động của nó như sau:
- Khi vào lần đầu tiên (A – trang gốc), bạn được trả lại trang challenge với một đoạn mã JS
- Browser sẽ phải execute đoạn mã và tính ra 1 con số kết quả
- Sau timeout là 5s, trang sẽ gửi kết quả tính được tới 1 trang xác nhận B
- Nếu kết quả chuẩn, browser sẽ nhận được từ request tới B một cookie tên là “cf_clearance”, đồng thời trên header của response sẽ redirect lại trang gốc A
- Lúc này do đã có cookie cf_clearance, nội dung đầy đủ sẽ được trả về
- Cookie sẽ hết hạn sau khoảng ~10 hours
Vậy để vượt qua challenge này, bạn sẽ phải tìm ra được cookie cf_clearance để gửi kèm request. Bạn sẽ không thể dùng lại cookie này do nó hết hạn liên tục, vì vậy cách duy nhất là phải giải mã được bài toán trong code js để tìm ra kết quả.
Trước khi tới code mẫu (PHP) để giải quyết vấn đề này, cùng tìm hiểu giải thuật cho vấn đề. Bạn có thể dựa vào đó mà viết code trên các ngôn ngữ và môi trường khác nhau.
Trong trang challenge, bạn sẽ thấy có form sau:
Trong form này, “s”, “jschl_vc” và “pass” là các tham số đầu vào của bài toán. “jschl-answer” là chỗ chứa kết quả. Sau khi đoạn JS được excecute thì kết quả sẽ được gắn vào “jschl-answer” và form sẽ được submit tới “/cdn-cgi/1/chk_jschl”. Các thông tin này hoàn toàn lấy được từ HTML của trang.
Tiếp theo, chúng ta cần hiểu thuật toán trong đoạn JS. Bỏ qua các đoạn lấy giá trị từ input, chúng ta quan tâm tới các dòng tính toán giá trị của biến HGCVWUY.ARADNEfj (biến này thay đổi liên tục nên mỗi lần request sẽ phải parse ra tên biến).
Các đoạn trong như thế này:
+((!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+[])+(!+[]+!![]+!![]+!![]+!![])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![])+(!+[]+!![]+!![]+!![]+!![])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![])+(!+[]+!![]+!![]+!![]+!![])+(+[])+(!+[]+!![]+!![]))/+((!+[]+!![]+!![]+!![]+[])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![])+(+[])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![])+(!+[]+!![])+(!+[]+!![])+(!+[]+!![]+!![]+!![]+!![])+(!+[]+!![]+!![]))
Thực chất là các phép tính cộng trừ. Nếu bạn paste nó vào cửa sổ Watch của Developer Console (F12), bạn sẽ thấy nó ra 1 kết quả float (ví dụ trên giá trị là 1.7854672274628525)
Như vậy ta chỉ cần mô phỏng các phương trình này bằng code PHP (hoặc ngôn ngữ của bạn chọn) là sẽ tính được. Phương pháp là:
- Thay thế ‘!+[]’, ‘+!![]’, ‘+![]’ thành giá trị +1
- Thay thế ‘+[]’ thành giá trị +0
- Dấu cộng giữa 2 ngoặc đơn ‘)+(‘ là phép nối chuỗi (trong PHP là “.”, trong C++ là &)
- Dấu ‘)/+(‘ giữa 2 ngoặc đơn là phép chia
Như vậy chỉ cần replace thông thường sẽ ra được phép toán cần thiết:
<?php function cf_formula_calculate($formula){ $formula = str_replace(array('!+[]', '+!![]', '+![]'), array('+1','+1','+1'), $formula); $formula = str_replace(array('+[]'), array('+0'), $formula); $formula = str_replace(array(')+(', ')/+('), array(').(', ')/('), $formula); return eval('return ' . $formula . ';'); } ?>
Bạn đọc thêm đoạn code JS sẽ thấy sẽ có 1 loạt các lần cộng trừ như thế này:
HGCVWUY.ARADNEfj+= …
HGCVWUY.ARADNEfj*= …
HGCVWUY.ARADNEfj-= …
Số lần cộng trừ này không cố định nên bạn cần phân tính chuỗi và tính vào kết quả cuối cùng. Sau khi đã có được giá trị của HGCVWUY.ARADNEfj thì kết quả cuối cùng sẽ được tính như sau:
a.value = +HGCVWUY.ARADNEfj.toFixed(10) + t.length
trong đó a là input ‘jschl-answer’, còn t là tên miền của site. (ví dụ trường hợp này là novelplanet.com). toFixed(10) nghĩa là lấy thập phân 10 chữ số, còn t.length trả về độ dài tên miền, ở đây là 15. Kết quả cuối cùng ở ví dụ trên là “16.1222265472”
Có kết quả cuối cùng rồi, ta gửi tiếp tới /cdn-cgi/1/chk_jschl cùng các tham số trong form để lấy được cf_clearance. Set cookie và gửi lại request tới trang gốc là sẽ lấy được nội dung. Cookie có thể dùng trong nhiều giờ, khi hết hạn thì bạn lại lặp lại quá trình trên để lấy lại cf_clearance mới.
Một ghi chú quan trọng là, bạn phải delay lời gọi tới /cdn-cgi/1/chk_jschl trong khoảng 5s (trong PHP sử dụng sleep(5)) để bắt chước cơ chế bảo mật của nó bởi vì nếu gửi luôn, CloudFlare sẽ nhận ra và không trả về cf_clearance.
Mã nguồn PHP đầy đủ bạn có thể xem ở đây: https://github.com/hadoanngoc/php-bypass-cloudflare-challenge Tôi sử dụng phiên bản modified của Simple HTML DOM để gửi và nhận cookies.
Nếu bạn dùng NodeJS, có một thư viện có sẵn ở đây: https://github.com/codemanki/cloudscraper
Nguồn: tekwoks