如何防止脚本注入攻击

Posted

技术标签:

【中文标题】如何防止脚本注入攻击【英文标题】:How to prevent script injection attacks 【发布时间】:2020-02-24 04:00:09 【问题描述】:

简介

这个话题一直是 *** 和许多其他技术论坛上许多问题和答案的祸根;但是,它们中的大多数都特定于特定条件,甚至更糟:据说通过dev-tools-consoledev-tools-elements 甚至address-bar 进行的脚本注入预防中的“全面”安全性被认为是“不可能”保护的。这个问题是为了解决这些问题,并作为当前和历史的参考,随着技术的进步 - 或发现新的/更好的方法来解决浏览器安全问题 - 特别是与script-injection 攻击相关。

疑虑

有很多方法可以“即时”提取或操作信息;具体来说,无论 SSL/TLS 是什么,都可以很容易地截获从输入中收集到的信息,并将其传输到服务器。

拦截示例

看看here 不管它多么“粗糙”,人们都可以很容易地使用这个原理来制作一个模板,只需在浏览器控制台中复制+粘贴到eval() 中就可以做各种令人讨厌的事情,例如:

console.log() 截获通过 XHR 传输的信息 操纵POST-data,更改用户引用,例如UUIDs 通过检查 JS 代码 cookiesheaders 将目标服务器替代 GET(和发布)请求信息提供给中继(或获取)信息

这种攻击在未经训练的人看来“似乎”微不足道,但当考虑到高度动态的界面时,这很快就会变成噩梦 - 等待被利用.

我们都知道“你不能信任前端”,服务器应该负责安全;但是 - 我们心爱的访客的隐私/安全呢?许多人在 javascript 中创建“一些快速应用程序”并且不知道(或关心)后端安全性。

保护前端和后端的安全将证明对普通攻击者来说是强大的,并且还减轻了服务器负载(在许多情况下)。

努力

Google 和 Facebook 都实施了一些缓解这些问题的方法,而且它们奏效了;所以这不是“不可能的”,但是,它们非常特定于各自的平台,并且实施需要使用整个框架以及大量工作 - 仅涵盖基础知识。

不管这些保护机制中的一些看起来多么“丑陋”;目标是在一定程度上帮助(减轻/预防)安全问题,使攻击者难以应对。众所周知:“你无法阻止黑客,你只能阻止他们的努力”

工具和要求

目标是拥有一套简单的工具(功能):

这些必须是纯(原版)javascript 它们一起不应超过几行代码(最多 200 行) 它们必须是immutable,以防止被攻击者“重新捕获” 这些不得与任何(流行的)JS 框架发生冲突,例如 React、Angular 等 不必“漂亮”,但至少可读,“单行”欢迎 跨浏览器兼容,至少达到很好的百分位数

【问题讨论】:

【参考方案1】:

运行时反射/自省

这是解决其中一些问题的一种方法,我并不认为它是“最好的”方法(根本),而是一种尝试。 如果可以拦截一些“可利用”的函数和方法,并查看“调用”(每次调用)是否来自产生它的服务器,那么这可能很有用,因为我们可以看到调用是否来自“来自稀薄的空气”(开发工具)。

如果要采用这种方法,那么首先我们需要一个函数来获取call-stack 并丢弃不是 FUBU 的函数(由我们自己)。如果这个函数的结果是空的,hazaa! - 我们没有拨打电话,我们可以继续进行。

一两个字

为了使这尽可能简短和简单,以下代码示例遵循 DRYKIS 原则,即:

不要重复自己,保持简单 “少代码”欢迎高手 “代码太多& cmets”吓跑大家 如果你能读懂代码 - 继续让它漂亮

话虽如此,请原谅我的“速记”,接下来会解释

首先我们需要一些常量和我们的 stack-getter

    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]elsep=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 个,其余的都是因为 .. 懒惰(简短回答) 两个参数都是可选的 如果第一个参数x 是一个数字,那么例如stack(0) 返回日志中的第一项,或 undefined 如果第二个参数astring - 或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; tryObject.defineProperty(o,k,c);catch(e)r=false;
    return r;
;

烘烤和钻孔 - 概要

这些都是不言自明的,所以,一些简单的例子就足够了

使用bore获取一个属性:console.log(bore(window,"XMLHttpRequest.prototype.open")) 使用bore设置一个属性:bore(window,"XMLHttpRequest.prototype.open",function()return "foo") 使用bore撕裂(不小心破坏):bore(window,"XMLHttpRequest.prototype.open",null) 使用bake 强化现有属性:bake(XMLHttpRequest.prototype,'open') 使用bake定义一个新的(硬)属性:bake(XMLHttpRequest.prototype,'bark',function()return "woof!")

拦截函数和构造

现在我们可以利用上述所有优势,因为我们设计了一个简单而有效的拦截器,绝不是“完美的”,但它应该就足够了;解释如下:

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);
    trybore(MAIN,k).prototype=Object.create(this[k].func.prototype)
    catch(e);
.bind();
jack() - 解释 它有2个参数,第一个作为字符串(用于bore),第二个作为拦截器(函数) 前几个 cmets 解释了一下 ..“add”行只是将另一个拦截器添加到同一个引用中 jack 释放现有函数,将其收起,然后使用“interceptor-functions”重播参数 拦截器可以返回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(开发工具)来修改某个元素的属性,给它一些 onclick 事件,然后我们的拦截器就赢了不明白;但是,我们可以使用mutation-observer 并使用该间谍来查看“属性更改”。在属性更改(或新节点)时,我们可以使用stak() 检查是否进行了更改FUBU

const watchDog=(new MutationObserver(function(l)

   if(!stak(0))alert("you again! :D");return;
));

watchDog.observe(document.documentElement,childList:true,subtree:true,attributes:true);

结论

这些只是处理坏问题的几种方法;虽然我希望有人觉得这很有用,但请随时编辑此答案,或发布更多(或替代/更好)提高前端安全性的方法。

【讨论】:

修复了可在以下环境中工作的代码:Firefox 和基于 Chromium 的浏览器(Chrome、Brave 等)。如果您遇到任何问题,请发表评论,我会解决它。

以上是关于如何防止脚本注入攻击的主要内容,如果未能解决你的问题,请参考以下文章

如何防止代码注入攻击在PHP

如何防止sql注入攻击?

php防止sql注入以及xss跨站脚本攻击

如何防止网站不被SQL注入攻击

如何实现php的安全最大化?怎样避免sql注入漏洞和xss跨站脚本攻击漏洞

如何防范sql注入攻击