Для контент-ориентированных компаний важна безопасность данных. Для контент-компаний важность данных невозможно переоценить. Например, если вы являетесь платформой для онлайн-обучения, данные темы очень важны, но они были украдены другими с помощью технологии сканирования? Если убрать основную конкурентоспособность, будет круто. Другой пример: независимый разработчик хочет скопировать ваш продукт, забрать ваши основные данные с помощью захвата пакетов и сканирования, а затем создать веб-сайт и приложение за короткий период времени и стать вашим сильным врагом в краткосрочной перспективе.
рептилия означает
- В настоящее время технология сканирования напрямую находит интересующий узел на отображаемой html-странице, а затем получает соответствующий текст.
- Некоторые веб-сайты хорошо защищены. Например, страницу списка может быть легко получить, но на странице сведений необходимо щелкнуть соответствующий элемент на странице списка, отправить itemId через форму формы, сервер генерирует соответствующие параметры и затем перенаправляет на страницу сведений (повторно после того, как направленный адрес приходит с параметром detailID страницы сведений), этот шаг может перехватить некоторых разработчиков сканеров
упражнятьсяТехническое решение для защиты от сканирования на стороне веб-сайта
С этих двух точек зрения (то, что вы видите на веб-странице, — это не то, что вы получаете, и бесполезно проверять интерфейсный запрос), я сформулировал следующий план защиты от сканирования.
-
Использовать HTTPS-протокол
-
Слишком много запросов за единицу времени, учетная запись блокируется
-
Ограничения передних технологий (следующая базовая технология)
# 比如需要正确显示的数据为“19950220”
1. 先按照自己需求利用相应的规则(数字乱序映射,比如正常的0对应还是0,但是乱序就是 0 <-> 1,1 <-> 9,3 <-> 8,...)制作自定义字体(ttf)
2. 根据上面的乱序映射规律,求得到需要返回的数据 19950220 -> 17730220
3. 对于第一步得到的字符串,依次遍历每个字符,将每个字符根据按照线性变换(y=kx+b)。线性方程的系数和常数项是根据当前的日期计算得到的。比如当前的日期为“2018-07-24”,那么线性变换的 k 为 7,b 为 24。
4. 然后将变换后的每个字符串用“3.1415926”拼接返回给接口调用者。(为什么是3.1415926,因为对数字伪造反爬,所以拼接的文本肯定是数字的话不太会引起研究者的注意,但是数字长度太短会误伤正常的数据,所以用所熟悉的 Π)
```
1773 -> “1*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “3*7+24” -> 313.1415926733.1415926733.141592645
02 -> "0*7+24" + "3.1415926" + "2*7+24" -> 243.141592638
20 -> "2*7+24" + "3.1415926" + "0*7+24" -> 383.141592624
```
# 前端拿到数据后再解密,解密后根据自定义的字体 Render 页面
1. 先将拿到的字符串按照“3.1415926”拆分为数组
2. 对数组的每1个数据,按照“线性变换”(y=kx+b,k和b同样按照当前的日期求解得到),逆向求解到原本的值。
3. 将步骤2的的到的数据依次拼接,再根据 ttf 文件 Render 页面上。
- Серверной части необходимо зашифровать данные в соответствии с протоколом, разработанным на предыдущем шаге.
Ниже сNode.jsВ качестве примера, чтобы объяснить, что должен делать бэкэнд
-
Сначала установите маршрутизацию интерфейса на бэкэнде
-
Получить параметры за маршрутом
-
Создавайте соответствующие данные на основе операторов SQL в соответствии с потребностями бизнеса. Если это цифровая часть, ее необходимо преобразовать в соответствии с методом, согласованным выше.
-
Преобразуйте сгенерированные данные в JSON и верните их вызывающей стороне
// json var JoinOparatorSymbol = "3.1415926"; function encode(rawData, ruleType) { if (!isNotEmptyStr(rawData)) { return ""; } var date = new Date(); var year = date.getFullYear(); var month = date.getMonth() + 1; var day = date.getDate(); var encodeData = ""; for (var index = 0; index < rawData.length; index++) { var datacomponent = rawData[index]; if (!isNaN(datacomponent)) { if (ruleType < 3) { var currentNumber = rawDataMap(String(datacomponent), ruleType); encodeData += (currentNumber * month + day) + JoinOparatorSymbol; } else if (ruleType == 4) { encodeData += rawDataMap(String(datacomponent), ruleType); } else { encodeData += rawDataMap(String(datacomponent), ruleType) + JoinOparatorSymbol; } } else if (ruleType == 4) { encodeData += rawDataMap(String(datacomponent), ruleType); } } if (encodeData.length >= JoinOparatorSymbol.length) { var lastTwoString = encodeData.substring(encodeData.length - JoinOparatorSymbol.length, encodeData.length); if (lastTwoString == JoinOparatorSymbol) { encodeData = encodeData.substring(0, encodeData.length - JoinOparatorSymbol.length); } }
//字体映射处理 function rawDataMap(rawData, ruleType) { if (!isNotEmptyStr(rawData) || !isNotEmptyStr(ruleType)) { return; } var mapData; var rawNumber = parseInt(rawData); var ruleTypeNumber = parseInt(ruleType); if (!isNaN(rawData)) { lastNumberCategory = ruleTypeNumber; //字体文件1下的数据加密规则 if (ruleTypeNumber == 1) { if (rawNumber == 1) { mapData = 1; } else if (rawNumber == 2) { mapData = 2; } else if (rawNumber == 3) { mapData = 4; } else if (rawNumber == 4) { mapData = 5; } else if (rawNumber == 5) { mapData = 3; } else if (rawNumber == 6) { mapData = 8; } else if (rawNumber == 7) { mapData = 6; } else if (rawNumber == 8) { mapData = 9; } else if (rawNumber == 9) { mapData = 7; } else if (rawNumber == 0) { mapData = 0; } } //字体文件2下的数据加密规则 else if (ruleTypeNumber == 0) { if (rawNumber == 1) { mapData = 4; } else if (rawNumber == 2) { mapData = 2; } else if (rawNumber == 3) { mapData = 3; } else if (rawNumber == 4) { mapData = 1; } else if (rawNumber == 5) { mapData = 8; } else if (rawNumber == 6) { mapData = 5; } else if (rawNumber == 7) { mapData = 6; } else if (rawNumber == 8) { mapData = 7; } else if (rawNumber == 9) { mapData = 9; } else if (rawNumber == 0) { mapData = 0; } } //字体文件3下的数据加密规则 else if (ruleTypeNumber == 2) { if (rawNumber == 1) { mapData = 6; } else if (rawNumber == 2) { mapData = 2; } else if (rawNumber == 3) { mapData = 1; } else if (rawNumber == 4) { mapData = 3; } else if (rawNumber == 5) { mapData = 4; } else if (rawNumber == 6) { mapData = 8; } else if (rawNumber == 7) { mapData = 3; } else if (rawNumber == 8) { mapData = 7; } else if (rawNumber == 9) { mapData = 9; } else if (rawNumber == 0) { mapData = 0; } } else if (ruleTypeNumber == 3) { if (rawNumber == 1) { mapData = ""; } else if (rawNumber == 2) { mapData = ""; } else if (rawNumber == 3) { mapData = ""; } else if (rawNumber == 4) { mapData = ""; } else if (rawNumber == 5) { mapData = ""; } else if (rawNumber == 6) { mapData = ""; } else if (rawNumber == 7) { mapData = ""; } else if (rawNumber == 8) { mapData = ""; } else if (rawNumber == 9) { mapData = ""; } else if (rawNumber == 0) { mapData = ""; } } else{ mapData = rawNumber; } } else if (ruleTypeNumber == 4) { var sources = ["年", "万", "业", "人", "信", "元", "千", "司", "州", "资", "造", "钱"]; //判断字符串为汉字 if (/^[\u4e00-\u9fa5]*$/.test(rawData)) { if (sources.indexOf(rawData) > -1) { var currentChineseHexcod = rawData.charCodeAt(0).toString(16); var lastCompoent; var mapComponetnt; var numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; var characters = ["a", "b", "c", "d", "e", "f", "g", "h", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]; if (currentChineseHexcod.length == 4) { lastCompoent = currentChineseHexcod.substr(3, 1); var locationInComponents = 0; if (/[0-9]/.test(lastCompoent)) { locationInComponents = numbers.indexOf(lastCompoent); mapComponetnt = numbers[(locationInComponents + 1) % 10]; } else if (/[a-z]/.test(lastCompoent)) { locationInComponents = characters.indexOf(lastCompoent); mapComponetnt = characters[(locationInComponents + 1) % 26]; } mapData = "&#x" + currentChineseHexcod.substr(0, 3) + mapComponetnt + ";"; } } else { mapData = rawData; } } else if (/[0-9]/.test(rawData)) { mapData = rawDataMap(rawData, 2); } else { mapData = rawData; } } return mapData; }
//api module.exports = { "GET /api/products": async (ctx, next) => { ctx.response.type = "application/json"; ctx.response.body = { products: products }; }, "GET /api/solution1": async (ctx, next) => { try { var data = fs.readFileSync(pathname, "utf-8"); ruleJson = JSON.parse(data); rule = ruleJson.data.rule; } catch (error) { console.log("fail: " + error); } var data = { code: 200, message: "success", data: { name: "@杭城小刘", year: LBPEncode("1995", rule), month: LBPEncode("02", rule), day: LBPEncode("20", rule), analysis : rule } } ctx.set("Access-Control-Allow-Origin", "*"); ctx.response.type = "application/json"; ctx.response.body = data; }, "GET /api/solution2": async (ctx, next) => { try { var data = fs.readFileSync(pathname, "utf-8"); ruleJson = JSON.parse(data); rule = ruleJson.data.rule; } catch (error) { console.log("fail: " + error); } var data = { code: 200, message: "success", data: { name: LBPEncode("建造师",rule), birthday: LBPEncode("1995年02月20日",rule), company: LBPEncode("中天公司",rule), address: LBPEncode("浙江省杭州市拱墅区石祥路",rule), bidprice: LBPEncode("2万元",rule), negative: LBPEncode("2018年办事效率太高、负面基本没有",rule), title: LBPEncode("建造师",rule), honor: LBPEncode("最佳奖",rule), analysis : rule } } ctx.set("Access-Control-Allow-Origin", "*"); ctx.response.type = "application/json"; ctx.response.body = data; }, "POST /api/products": async (ctx, next) => { var p = { name: ctx.request.body.name, price: ctx.request.body.price }; products.push(p); ctx.response.type = "application/json"; ctx.response.body = p; } };
//路由 const fs = require("fs"); function addMapping(router, mapping){ for(var url in mapping){ if (url.startsWith("GET")) { var path = url.substring(4); router.get(path,mapping[url]); console.log(`Register URL mapping: GET: ${path}`); }else if (url.startsWith('POST ')) { var path = url.substring(5); router.post(path, mapping[url]); console.log(`Register URL mapping: POST ${path}`); } else if (url.startsWith('PUT ')) { var path = url.substring(4); router.put(path, mapping[url]); console.log(`Register URL mapping: PUT ${path}`); } else if (url.startsWith('DELETE ')) { var path = url.substring(7); router.del(path, mapping[url]); console.log(`Register URL mapping: DELETE ${path}`); } else { console.log(`Invalid URL: ${url}`); } } } function addControllers(router, dir){ fs.readdirSync(__dirname + "/" + dir).filter( (f) => { return f.endsWith(".js"); }).forEach( (f) => { console.log(`Process controllers:${f}...`); let mapping = require(__dirname + "/" + dir + "/" + f); addMapping(router,mapping); }); } module.exports = function(dir){ let controllers = dir || "controller"; let router = require("koa-router")(); addControllers(router,controllers); return router.routes(); };
-
Внешний интерфейс расшифровывает данные в обратном порядке в соответствии с данными, возвращаемыми сервером.
$("#year").html(getRawData(data.year,log)); // util.js var JoinOparatorSymbol = "3.1415926"; function isNotEmptyStr($str) { if (String($str) == "" || $str == undefined || $str == null || $str == "null") { return false; } return true; } function getRawData($json,analisys) { $json = $json.toString(); if (!isNotEmptyStr($json)) { return; } var date= new Date(); var year = date.getFullYear(); var month = date.getMonth() + 1; var day = date.getDate(); var datacomponents = $json.split(JoinOparatorSymbol); var orginalMessage = ""; for(var index = 0;index < datacomponents.length;index++){ var datacomponent = datacomponents[index]; if (!isNaN(datacomponent) && analisys < 3){ var currentNumber = parseInt(datacomponent); orginalMessage += (currentNumber - day)/month; } else if(analisys == 3){ orginalMessage += datacomponent; } else{ //其他情况待续,本 Demo 根据本人在研究反爬方面的技术并实践后持续更新 } } return orginalMessage; }
Например, бэкенд возвращает 323.14743.14743.1446, по нашему согласованному алгоритму результат может быть 1773
-
Отрисовка страницы в соответствии с файлом ttf
Вычисленный выше 1773, а то по ttf файлу страница видит 1995 -
Затем, чтобы сканеры не могли просматривать проблемы исследования JS, файлы JS шифруются. Если ваш технологический стек Vue, React и т. д., webpack предоставляет вам плагин для шифрования JS, который также очень удобен в обращении.
-
Лично я не считаю этот метод очень безопасным. Поэтому я подумал о комбинации различных планов. Например
Обновленная версия защиты от скалолазания
Лично я чувствую, что если дело касается опытного разработчика краулеров, вышеприведенное решение все еще может быть взломано, поэтому обновленная версия делается на основе предыдущей.
- Комбинация 1: файл шрифта не должен быть исправлен.Хотя запрошенная ссылка та же самая,модуль берется в соответствии с последним числом текущей метки времени.Например,модуль 4 берется в Демо, а есть 4 значения 0, 1, 2 и 3. Эти 4 значения соответствуют разным файлам шрифтов, поэтому, когда краулер ломал голову, чтобы доползти до шрифта в одном случае, он не ожидал запросить его повторно, и правила файла шрифта изменились 😂
- Комбинация 2: Предыдущее правило заключается в том, что проблема со шрифтом не в порядке, но нарушается только соответствие чисел. Например1 -> 4, 5 -> 8. Следующая процедура заключается в том, что каждое число соответствуетЮникод кодЗатем нужно сделать шрифт можно .ttf, .woff и так далее.
Такие комбинации сбивают с толку. Для обычных рептилий он заброшен.
Противозалезающие средства для апгрейда
Перечисленные выше методы в основном направлены наномерЧто мне делать, если я хочу отменить восхождение китайских иероглифов? Далее несколько вариантов
-
план 1:Для облака слов с наибольшей частотой на вашем сайте сделайте карту китайских иероглифов, то есть файл пользовательского шрифта, шаги такие же, как и числа. Сначала создайте соответствующие файлы ttf для часто используемых китайских иероглифов; преобразуйте файлы ttf в файлы svg по приведенным ниже ссылкам, а затем выберите ранее созданные файлы svg на веб-сайте, где щелкнут ссылку «сопоставление шрифтов» ниже, и поместите файлы svg в файл svg.Сделайте сопоставление для каждого китайского символа , то есть китайский символ является специальным кодом юникода (обратите внимание, что код юникода здесь не должен быть напрямую сгенерирован онлайн, потому что напрямую сгенерированные вещи также Метод, который я даю, заключается в том, чтобы сначала использовать веб-сайт для его создания, а затем внести простое изменение в полученный результат, например, преобразовать «e342» в «e231»); затем данные, возвращаемые интерфейсом, отображаются в обратном порядке в соответствии с к правилам нашего файла шрифта.
-
Сценарий 2:Важные шрифты веб-сайта генерируются в изображения из html-части, поэтому сканеру необходимо идентифицировать требуемый контент с большими затратами и использовать OCR. КПД тоже низкий. Таким образом, вы можете перехватить некоторые сканеры
-
Сценарий 3:См. Обмен технологиями Ctrip «Наивысший уровень защиты от лазания — это отпечаток Canvas. Принцип заключается в том, что разные машины и разное оборудование всегда имеют ошибки на уровне пикселей в картинках, нарисованных Canvas. Поэтому мы судим, что когда большое количество холсты обращаются для доступа, всегда есть ошибки на уровне пикселей.Если отпечатки пальцев совпадают, это считается рептилией, и ее можно заблокировать».
Я реализовал решение 1 в демо.
ключевой шаг
- Сначала найдите часто используемые ключевые слова в соответствии с вашими продуктами, сгенерируйтеоблако слов
- В соответствии с облаком слов сгенерируйте соответствующий код юникода для каждого слова.
- Превратите китайские иероглифы, включенные в облако слов, в библиотеку шрифтов.
- Сделайте библиотеку шрифтов .ttf в формате svg и загрузите ее наicomoonСоздавайте пользовательские шрифты, но с правилами, например"год"соответствующийюникод кодда">74", но нам нужно сделатьШифрование Цезаря, например, мы устанавливаемКомпенсироватьравно 1, то послеШифрование Цезаря "год"соответствующийunicodeкод">75". Используйте это правило, чтобы сделать нужную нам библиотеку шрифтов
- Что сервер делает каждый раз, когда вызывается интерфейс: сервер инкапсулирует метод и оценивает, находятся ли данные в облаке слов с помощью метода Если это символ в облаке слов, используйте правила (найдите код юникода соответствующий китайскому иероглифу) , а затем установить соответствующее смещение по шифрованию Цезаря, 1 в Демо, зашифровать каждый китайский иероглиф) и вернуть данные после шифрования
- Что делает клиент:
- Сначала импортируйте библиотеку шрифтов китайских иероглифов, которую мы создали ранее.
- Вызовите интерфейс, чтобы получить данные и отобразить их на соответствующем узле Dom.
- Если это китайский текст, мы устанавливаем класс css соответствующего узла в класс китайских символов, а семейство шрифтов, соответствующее этому классу, представляет собой библиотеку шрифтов китайских символов, которую мы представили выше.
//style.css
@font-face {
font-family: "NumberFont";
src: url('http://127.0.0.1:8080/Util/analysis');
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@font-face {
font-family: "CharacterFont";
src: url('http://127.0.0.1:8080/Util/map');
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h2 {
font-family: "NumberFont";
}
h3,a{
font-family: "CharacterFont";
}
портал
Шаги по созданию шрифтов,ttf в svg,правила сопоставления шрифтов
достигнутый эффект
- Данные, отображаемые на странице, не соответствуют результатам, отображаемым элементом проверки.
- Просмотр данных интерфейса не соответствует элементам аудита и увиденному интерфейсу
- Более противоречивые результаты перед каждым обновлением страницы
- Методы обработки чисел и китайских иероглифов несовместимы.
Такие комбинации сбивают с толку. Для обычных рептилий он заброшен.
Предыдущий веб-сайт ttf to svg будет ограничивать преобразование, когда файл ttf слишком велик, и позволит вам купить его.Новая ссылка будет размещена ниже.
Демонстрационный адрес
бег шаг
//客户端。先查看本机 ip 在 Demo/Spider-develop/Solution/Solution1.js 和 Demo/Spider-develop/Solution/Solution2.js 里面将接口地址修改为本机 ip
$ cd Demo
$ ls
REST Spider-release file-Server.js
Spider-develop Util rule.json
$ node file-Server.js
Server is runnig at http://127.0.0.1:8080/
//服务端 先安装依赖
$ cd REST/
$ npm install
$ node app.js