Runtime Reflection / Introspection
Это способ решения некоторых из этих проблем, и я не утверждаю, что это «лучший» способ (вообще), это попытка. Если бы кто-то мог перехватить некоторые «эксплуатируемые» функции и методы и посмотреть, был ли сделан «вызов» (за вызов) с сервера, который его породил, или нет, то это могло бы оказаться полезным, так как мы можем видеть, пришел ли вызов «изthin air "(dev-tools).
Если этот подход будет принят, то сначала нам понадобится функция, которая захватывает call-stack
и отбрасывает то, что не является FUBU (длянас от нас). Если результат этой функции пустой, хазаа! - мы не сделали вызов, и мы можем продолжить соответственно.
слово или два
Чтобы сделать это максимально коротким и простым, следующие примеры кода следуют DRYKIS принципы, которые:
- не повторяйте себя, не усложняйте
- "меньше кода" приветствует адепта
- «слишком много кода и комментариев» отпугивают всех
- , если вы можете читать код - продолжайте и сделайте его красивым
Сказав это, простите мою "короткую руку",объяснение будет следовать
сначала нам понадобятся некоторые константы и наш стек-получатель
const MAIN = window;
const VOID = (function(){}()); // paranoid
const HOST = `https://${location.host}`; // if not `https` then ... ?
const stak = function(x,a, e,s,r,h,o)
{
a=(a||''); e=(new Error('.')); s=e.stack.split('\n'); s.shift(); r=[]; h=HOSTPURL; o=['_fake_']; s.forEach((i)=>
{
if(i.indexOf(h)<0){return}; let p,c,f,l,q; q=1; p=i.trim().split(h); c=p[0].split('@').join('').split('at ').join('').trim();
c=c.split(' ')[0];if(!c){c='anon'}; o.forEach((y)=>{if(((c.indexOf(y)==0)||(c.indexOf('.'+y)>0))&&(a.indexOf(y)<0)){q=0}}); if(!q){return};
p=p[1].split(' '); f=p[0]; if(f.indexOf(':')>0){p=f.split(':'); f=p[0]}else{p=p.pop().split(':')}; if(f=='/'){return};
l=p[1]; r[r.length]=([c,f,l]).join(' ');
});
if(!isNaN(x*1)){return r[x]}; return r;
};
После сжимания, не забывая, это было написано "на лету" как "доказательство концепции", пока проверено и работает. Отредактируйте по своему желанию.
stak()
- краткое объяснение
- только 2 релевантных аргумента - это первые 2, остальные - потому что ... лень (короткий ответ)
- оба аргумента являются необязательными
- если 1-й аргумент
x
является числом, то, например, stack(0)
возвращает 1-й элемент в журнале или undefined
- , если2-й аргумент
a
является либо string
-или array
, то, например, stack(undefined, "anonymous")
допускает «анонимный», даже если он был «опущен» в o
- остальная часть кода просто быстро анализирует стек, это должно работать как в браузерах, основанных на webkit, так и в gecko (chrome & firefox)
- В результате получается массив строк, каждая строка представляет собой запись журнала, разделеннуюодин пробел как
function file line
- , если имя домена не найдено в записи журнала (часть имени файла перед синтаксическим анализом), тогда оно не будет в результате
- по умолчаниюон игнорирует имя файла
/
(точно), поэтому, если вы протестируете этот код, помещение в отдельный файл .js
даст лучшие результаты, чем вindex.html
(обычно) - или какой бы механизм веб-корня ни использовался - пока не беспокойтесь о
_fake_
, теперь он находится в функции jack
ниже
сейчаснам нужны некоторые инструменты
bore()
- получить / установить / скопировать некоторое значение объекта по строковой ссылке
const bore = function(o,k,v)
{
if(((typeof k)!='string')||(k.trim().length<1)){return}; // invalid
if(v===VOID){return (new Function("a",`return a.${k}`))(o)}; // get
if(v===null){(new Function("a",`delete a.${k}`))(o); return true}; // rip
(new Function("a","z",`a.${k}=z`))(o,v); return true; // set
};
bake()
- сокращение для усиления существующих свойств объекта (или определения новых)
const bake = function(o,k,v)
{
if(!o||!o.hasOwnProperty){return}; if(v==VOID){v=o[k]};
let c={enumerable:false,configurable:false,writable:false,value:v};
let r=true; try{Object.defineProperty(o,k,c);}catch(e){r=false};
return r;
};
bake& bore - rundown
Это неверное объяснение, поэтому некоторых быстрых примеров должно хватить
- , используя
bore
to get свойство: console.log(bore(window,"XMLHttpRequest.prototype.open"))
- с использованием
bore
до установить свойство: bore(window,"XMLHttpRequest.prototype.open",function(){return "foo"})
- с использованием
bore
до rip (уничтожить небрежно): bore(window,"XMLHttpRequest.prototype.open",null)
- используя
bake
до укрепить существующее свойство: bake(XMLHttpRequest.prototype,'open')
- используя
bake
до определить новое (сложное) свойство: bake(XMLHttpRequest.prototype,'bark',function(){return "woof!"})
перехватывающие функции и конструкции
Теперь мы можем использовать все вышеперечисленное в наших интересах, поскольку мы разрабатываем простой, но эффективный перехватчик, отнюдь не "perfect ", но этого должно быть достаточно;пояснение следующее:
const jack = function(k,v)
{
if(((typeof k)!='string')||!k.trim()){return}; // invalid reference
if(!!v&&((typeof v)!='function')){return}; // invalid callback func
if(!v){return this[k]}; // return existing definition, or undefined
if(k in this){this[k].list[(this[k].list.length)]=v; return}; //add
let h,n; h=k.split('.'); n=h.pop(); h=h.join('.'); // name & holder
this[k]={func:bore(MAIN,k),list:[v]}; // define new callback object
bore(MAIN,k,null); let f={[`_fake_${k}`]:function()
{
let r,j,a,z,q; j='_fake_'; r=stak(0,j); r=(r||'').split(' ')[0];
if(!r.startsWith(j)&&(r.indexOf(`.${j}`)<0)){fail(`:(`);return};
r=jack((r.split(j).pop())); a=([].slice.call(arguments));
for(let p in r.list)
{
if(!r.list.hasOwnProperty(p)||q){continue}; let i,x;
i=r.list[p].toString(); x=(new Function("y",`return {[y]:${i}}[y];`))(j);
q=x.apply(r,a); if(q==VOID){return}; if(!Array.isArray(q)){q=[q]};
z=r.func.apply(this,q);
};
return z;
}}[`_fake_${k}`];
bake(f,'name',`_fake_${k}`); bake((h?bore(MAIN,h):MAIN),n,f);
try{bore(MAIN,k).prototype=Object.create(this[k].func.prototype)}
catch(e){};
}.bind({});
jack()
- объяснение
- принимает 2 аргумента: первый - как строка (используется для
bore
), второй - как перехватчик (функция) - первые несколько комментариев объясняют немного ... строка «добавить» просто добавляет другой перехватчик к той же ссылке
jack
удаляет существующую функцию, убирает ее, затем использует «функции-перехватчики» дляаргументы воспроизведения - перехватчики могут либо вернуть
undefined
, либо значение, если ни одно из значений не возвращается, исходная функция не вызывается - первое значение, возвращаемое перехватчиком, используется как аргумент (ы) для вызова оригинала, а возвращение является результатом для вызывающего / вызывающего
- , что
fail(":(")
является преднамеренным;Ошибка будет выдана, если у вас нет этой функции - только если jack()
не удалось.
Примеры
Давайте запретим использование eval
в консоли -или адресная строка
jack("eval",function(a){if(stak(0)){return a}; alert("having fun?")});
расширяемость
Если вы хотите DRY-er способ взаимодействия с jack
, следующее проверено и хорошо работает:
const hijack = function(l,f)
{
if(Array.isArray(l)){l.forEach((i)=>{jack(i,f)});return};
};
Теперь вы можете перехватывать массово, например так:
hijack(['eval','XMLHttpRequest.prototype.open'],function()
{if(stak(0)){return ([].slice.call(arguments))}; alert("gotcha!")});
Умный атакующий может затем использовать Elements (dev-инструмент), чтобы изменить атрибут некоторого элемента, передав ему событие onclick
, тогда наш перехватчик не поймает этого;однако, мы можем использовать наблюдатель мутации и с этим шпионом «изменения атрибутов». После изменения атрибута (или нового узла) мы можем проверить, были ли внесены изменения FUBU (или нет) с помощью нашей stak()
проверки:
const watchDog=(new MutationObserver(function(l)
{
if(!stak(0)){alert("you again! :D");return};
}));
watchDog.observe(document.documentElement,{childList:true,subtree:true,attributes:true});
Заключение
Это было всего лишь несколько способов справиться с плохой проблемой;хотя я надеюсь, что кто-то найдет это полезным, и, пожалуйста, не стесняйтесь редактировать этот ответ или публиковать более (или альтернативные / лучшие) способы повышения безопасности интерфейса.