如何防止脚本注入攻击

2024-05-05

Intro

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

Concerns

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

intercept example

看一看here https://stackoverflow.com/questions/58459470/php-how-to-check-if-request-is-for-js-worker/58464693#58464693不管它多么“粗糙”,人们可以轻松地利用这一原理来制作一个模板,只需复制+粘贴到eval()在浏览器控制台中做各种令人讨厌的事情,例如:

  • console.log()截获传输中的信息XHR
  • 操纵POST-数据,更改用户引用,例如UUIDs
  • 提供目标服务器替代方案GET(和发布)请求信息以通过检查 JS 代码来中继(或获取)信息,cookies and headers

对于未经训练的人来说,这种攻击“似乎”微不足道,但是当涉及高度动态的界面时,这很快就会成为一种攻击恶梦-等待被利用。

我们都知道“你不能相信前端”服务器应对安全负责;然而 - 我们心爱的访客的隐私/安全怎么样?许多人用 JavaScript 创建“一些快速应用程序”,但不知道(或不关心)后端安全性。

事实证明,保护前端和后端的安全对于普通攻击者而言是非常强大的,并且还可以减轻服务器负载(在许多情况下)。

Efforts

谷歌和 Facebook 都实施了一些缓解这些问题的方法,而且它们确实有效。所以这不是“不可能”,但是,它们对于各自的平台来说非常具体,并且实现需要使用整个框架加上大量的工作 - 仅涵盖基础知识。

不管其中一些保护机制看起来多么“丑陋”;目标是help在一定程度上(减轻/防止)安全问题,使攻击者难以应对。现在大家都知道了:“你无法阻止黑客,你只能阻止他们的努力”.

工具和要求

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

  • 这些必须是普通(vanilla)javascript
  • 它们加在一起不应超过几行代码(最多 200 行)
  • 他们必须是immutable,防止攻击者“重新捕获”
  • 这些不得与任何(流行的)JS 框架冲突,例如 React、Angular 等
  • 不一定要“漂亮”,但至少可读,欢迎“单行”
  • 跨浏览器兼容,至少达到良好的百分位

运行时反思/内省

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

如果要采用这种方法,那么首先我们需要一个函数来获取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() - short explanation
  • 唯一的2个相关参数是第1个2,其余的是因为..懒惰(简短回答)
  • 两个参数都是可选的
  • 如果第一个参数x是一个数字,那么例如stack(0)返回日志中的第一项,或者undefined
  • 如果第二个参数a是一个string -or an array那么例如stack(undefined, "anonymous") allows“匿名”,尽管它被“省略”o
  • 其余代码只是快速解析堆栈,这应该适用于基于 webkit 和 gecko 的浏览器(chrome 和 firefox)
  • 结果是一个字符串数组,每个字符串都是一个由单个空格分隔的日志条目,如下所示function file line
  • 如果在日志条目(解析之前的文件名的一部分)中找不到域名,那么它不会出现在结果中
  • 默认情况下它忽略文件名/(完全正确)所以如果你测试这段代码,放入一个单独的.js文件将产生比在中更好的结果index.html(通常)-或使用任何网络根机制
  • 别担心_fake_目前,它位于jack下面的函数

现在我们需要一些工具

bore() - get/set/rip some value of an object by string reference
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() - shorthand to harden existing object properties (or define new ones)
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;
};

烘烤和钻孔 - 概要

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

  • using bore to get一个属性:console.log(bore(window,"XMLHttpRequest.prototype.open"))
  • using bore to set一个属性:bore(window,"XMLHttpRequest.prototype.open",function(){return "foo"})
  • using bore to rip(不小心毁掉):bore(window,"XMLHttpRequest.prototype.open",null)
  • using bake to harden现有财产:bake(XMLHttpRequest.prototype,'open')
  • using bake to define一个新的(硬)属性: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);
    try{bore(MAIN,k).prototype=Object.create(this[k].func.prototype)}
    catch(e){};
}.bind({});
jack() - explanation
  • 它需要 2 个参数,第一个为字符串(用于bore),第二个用作拦截器(函数)
  • 前几条评论解释了一点..“add”行只是将另一个拦截器添加到同一引用
  • jack废弃现有函数,将其收起,然后使用“拦截器函数”重播参数
  • 拦截器可以返回undefined或一个值,如果没有返回任何值,则不会调用原始函数
  • 拦截器返回的第一个值用作调用原始值的参数,并将结果返回给调用者/调用者
  • that fail(":(")是故意的;如果您没有该功能,则会抛出错误 - 仅当jack() failed.

Examples

我们来预防一下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事件,那么我们的拦截器将无法捕获该事件;但是,我们可以使用突变观察者 https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver并监视“属性变化”。在属性更改(或新节点)时,我们可以检查是否进行了更改FUBU(或不)与我们的stak() check:

const watchDog=(new MutationObserver(function(l)
{
   if(!stak(0)){alert("you again! :D");return};
}));

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

结论

这些只是处理严重问题的几种方法;尽管我希望有人发现这很有用,并且请随意编辑这个答案,或者发布更多(或替代/更好)的提高前端安全性的方法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何防止脚本注入攻击 的相关文章

  • 如何使用 HTML 5 实现类似 gmail 的文件上传/附件

    我记得一些支持 Ajax 之类的选项 无回发世界 文件上传 隐藏的 iframe 使用 flash 对象 尽管我仍然好奇为什么使用 SWF 以及它提供什么优势 然而 通过查看博客 HTML 5 似乎很有前途 我尝试了一些小示例 它确实有效
  • Angular UI select:从远程服务获取数据

    我正在使用角度用户界面选择 https github com angular ui ui select https github com angular ui ui select 我查看了演示的可用位置这个笨蛋 http plnkr co
  • 类型错误:类扩展值未定义不是函数或 null

    尝试创建这些实体时出现以下错误 TypeError Class extends value undefined is not a function or null 我假设这与循环依赖有关 但是在使用表继承和一对多关系时应该如何避免这种情况
  • 在随机位置启动 HTML5

    我有一个大约 2 小时长的音轨 我想在我的网站上使用它 我希望它在页面加载时在随机位置开始播放曲目 使用 HTML5 可以吗 我知道您可以使用 element currentTime 函数来获取当前位置 但是如何在完全下载之前获取曲目的总时
  • 水平滚动的表格上的“粘性”标题......完全不可能?

    经过过去几个小时的研究后 我开始认为这是不可能的 即使在最新的浏览器上也是如此 HTML table具有水平滚动的元素 带有 粘性 thead在顶部 作为垂直滚动的周围网页的一部分 这是我的尝试 a height 100px backgro
  • 如何立即启动setInterval循环? [复制]

    这个问题在这里已经有答案了 在一个简单的setInterval setInterval function Do something every 9 seconds 9000 第一个动作将在 9 秒后发生 t 9s 如何强制循环立即执行第一个
  • jQuery输入文件点击方法和IE上拒绝访问

    我尝试仅使用一个按钮作为输入文件 它在 Firefox Chrome Safari 中工作正常 但在 IE 中不行 提交表单时我总是收到 访问被拒绝 的消息 代码 input file click 有真正的解决方法吗 我在谷歌上浪费了大约2
  • 此页面上的脚本导致 ie 运行缓慢

    问题就在标题中 IE 行为异常 并说有一个脚本运行缓慢 FF 和 Chrome 没有这个问题 我怎样才能找到问题所在 那个页面有很多JS 手动检查不是一个好主意 EDIT 这是我正在处理的一个项目的页面 但我需要一个工具来查找问题 End
  • 检测 Google 验证码的挑战窗口何时关闭

    我正在使用谷歌隐形验证码 有没有办法检测挑战窗口何时关闭 我所说的挑战窗口是指您必须选择一些图像进行验证的窗口 目前 我在按钮上放置了一个旋转器 一旦单击按钮 就会呈现验证码挑战 无法向用户提示另一个质询窗口 我以编程方式调用渲染函数 gr
  • C# 和 Javascript SHA256 哈希的代码示例

    我有一个在服务器端运行的 C 算法 它对 Base64 编码的字符串进行哈希处理 byte salt Convert FromBase64String serverSalt Step 1 SHA256Managed sha256 new S
  • 如何在 Web 服务器上设置 gzip 压缩?

    我有一个嵌入式网络服务器 总共有 2 兆空间 通常 您使用 gzip 文件对客户端有利 但这会节省我们在服务器上的空间 我读到你可以只 gzip js 文件并将其保存在服务器上 我在 IIS 上测试过 但没有任何运气 为了使这项工作成功 我
  • 如何使用 window.onerror 捕获所有 javascript 错误? (包括道场)

    这个问题是后续问题javascript 如何在弹出警报中显示脚本错误 https stackoverflow com questions 2604976 javascript how to display script errors in
  • 呃!尝试将包发布到 npm 时出现 403

    我正在尝试将包发布到 npm 您可以在此处查看存储库 https github com biowaffeln mdx state https github com biowaffeln mdx state 我登录到 npmnpm login
  • 如何为我的整个 Node.js 应用程序使用相同的 MySQL 连接?

    我有一个app js 我从那里运行我的整个应用程序 在 app js 内部 我require许多文件中都有代码 对于每个文件 我都这样做 var mysql require mysql var mclient mysql createCon
  • 如何重复 ajax 请求,直到满足 RxJS Observable 的条件?

    我正在尝试重复请求 直到响应包含使用 RxJS 的数据 此时我想调用成功 或失败 处理程序 但我在使用 RxJS 时遇到了麻烦 这是我目前的方法 redux observable action observable mergeMap gt
  • Service Worker 与 Shared Worker

    Service Worker 和 Shared Worker 有什么区别 我什么时候应该使用 Service Worker 而不是 Shared Worker 反之亦然 Service Worker 具有共享 Worker 之外的附加功能
  • 尝试使用 Javascript 解决对称差异

    我正在尝试找出对称的解决方案 使用 javascript 完成以下任务的差异 目标 接受未指定数量的数组作为参数 保留数组中数字的原始顺序 不删除单个数组中数字的重复项 删除数组中出现的重复项 因此 例如 如果输入是 1 1 2 6 2 3
  • Chrome 扩展:强制 popup.html 关闭

    我想知道是否可以强制 popup html 关闭 在弹出的 javascript 中 window close
  • 如何在运行脚本之前提交活动单元格中所做的更改? (Google 表格/Google Apps 脚本)

    我正在使用 Google Apps 脚本在 Google 表格中创建提交表单 该表单位于一页上 提交内容被移至第二个隐藏页面 当用户填写表单后 他们按下提交页面上的按钮以激活脚本 我遇到的问题是 当用户填写最后一个单元格然后单击按钮时 输入
  • 如何在 Jquery/Javascript 中绑定模糊和更改,但只触发一次函数?

    我试图在选择元素更改时触发函数 由于 Ipad 在 on change 方面遇到问题 我还想绑定到 blur 这在 Ipad 上工作得很好 但是我不希望两个事件都触发该函数两次 所以我需要某种挂钩来确保两个事件是否都触发change and

随机推荐