Code Monkey home page Code Monkey logo

blog's Introduction

从手头上的一点点小小的东西开始整理,一步一步往上爬,随着时间的流逝,总有一天肯定能成为一只老蜗牛🐌,哈哈。

javascript

css/html

工具

优化,永远滴神

设计模式

网络协议/web安全

落地实践

blog's People

Contributors

hanyitim avatar

Watchers

 avatar  avatar

blog's Issues

app下载链接适配

app下载链接适配

背景

  • app的推广页需要有一个下载链接,方便用户点击的时候直接下载app,但是不同的系统下载的地方不一样;

方案

通过userAgent判断具体是哪个系统,是否在微信内打开,然后通过调整respones解决;

  1. 通过node服务
  2. 通过Nginx

以上两种方式都可以解决这个问题,基于一些环境原因,选择用Nginx的方式去解决

Nginx

server {
    listen    80;
    server_name  www.a.com a.com;
    charset utf-8;


    location /applink {
        #微信浏览器
        if ($http_user_agent ~* "micromessenger" ){
                rewrite ^/.*    https://a.com/ulink/action/  redirect;
        }
        if ($http_user_agent ~* "android"){
                rewrite ^/.*    https://a.com/apk/appname.apk redirect;
        }
        
        if ($http_user_agent ~* "iphone"){
                rewrite ^/.*    https://apps.apple.com/cn/app/ redirect;
        }
        rewrite ^/.*    http://lizhifm.cn/d  redirect;
    }
    
}

cookie的SameSite与HttpOnly

cookie的SameSite与HttpOnly

在对web安全问题的了解过程中,会发现很多时候对于cookie的管理不到位,会导致很多可以被攻击的点,下面从防御的出发点,了解几个cookie的使用特性,规避安全风险

httpOnly

概念:

禁止js通过Document.cookie 访问 cookie,但在发送请求时,cookie会在http header cookie里带上;

作用

防范XSS攻击

SameSite

概念:

SameSite 是 HTTP响应头 Set-Cookie的属性之一。用来声明该Cookie是否仅限与一方或者同一站点上下文;

CSRF攻击小案例

用户访问了A站点,并登陆了账号,这时,服务写了个用户身份标识的cookie

Set-Cookie: sessionid=38afes7a8;Path=/

接着,用户打开了恶意网站,恶意网站利用静态资源加载的漏洞,调用了A站点的api,这时,浏览器会把A站点的cookie一起发送,用户在不知情的情况下,被执行了某些操作

<html>
    <body>
        <img src="http://a.com/api/delete/xsdfsd"
    </body>
</html>

SameSite防御

从小案例内看到,如果浏览器能知道第三方站点访问A站点,不发送Cookie不就可以了;基于这个想法,简单了解一下SameSite的几个值;

  • Lax --- Cookies允许与顶级导航仪器发送,并将与第三方网站发起的GET请求一起发送,该值是浏览器默认。
  • Strict --- Cookie只会在第一方上下文中发送,不会与第三方发起的请求一起发送。
  • None --- Cookie将在所有上下文中发送,即允许跨域发送

从上面三个属性的信息看,如果想规避这个风险,那么应该是这样设置cookie

Set-Cookie: sessionid=38afes7a8; HttpOnly; Path=/;SameSite=Strict

参考

MDN文档

跨域方案整理

跨域方案整理

在前后端分离的大背景下,跨域是非常常见的一个问题,简单整理一下对应解决的方案。

概要

一、同源策略

同源策略是一个安全策略,限制了一个orgin的文档或者他加载的脚本如何与另外一个源的资源进行交互,可以杜绝恶意文档,减少可能被攻击的媒介。

怎么判断是否同源

两个Url的 protocol,port和host都是相同的话,那么这两个Url是同源,反之则不同源。

二、跨域请求

当文档对不同源的服务发起数据交互,那么这个时候发的就是跨域请求。

注:1. 跨域是浏览器的一个自身的行为,出发点是web安全。

解决方案

一、CORS(跨域资源共享)

使用额外的http头告诉浏览器,让当前orgin的web应用可以访问不同源服务器上的指定资源。

**QA:**为什么有些请求在浏览器的调试工具network面板有多一个options?

**AN:**因为通常跨域请求可以非为“简单请求”以及“非简单请求”,在发起非简单请求的时候,浏览器会事先发一个预检请求(options)询问源服务器是否能访问对应的资源;

简单请求的满足条件
  1. 请求方法为 GET、HEAD、PUT
  2. 头部字段只能包含
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(有额外限制)
    • DPR
    • Downlink
    • Save-Data
    • Vieport-Width
    • Width
  3. Content-Type仅限于以下三者
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
  4. 请求中的任意XMLHttpRequestUpload 对象均没有主持任何监听事件
  5. 请求中没使用ReadableStream对象

以上简单请求的一个满足条件,只要有一条满足不了,那么就属于非简单请求,在发起跨域请求的时候,浏览器就是发送预检请求。

处理

以下是基于nodejs express 的处理,基于 中间件 cors

普通处理

var express = require('express');
var cors = require('cors');
var app = express();

/*
cors 中间件的默认配置,
defaults = {
    origin: '*',
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
    preflightContinue: false,
    optionsSuccessStatus: 204
};
*/
app.use(cors());

只允许a.com这个根域名跨域请求

app.use(cors({
    origin:function(origin,callback){
        callback(null,/^\w+?\.a\.com$/.test(orign));
    }
}))

需要跨域带cookie的情况

  1. 动态设置origin
  2. credentials设置为true
  3. 客户端
    1. ajax 需要带 credentials:true 的头部
    2. cookie共享必须基于同根域名且cookie的domain配置成根域。
app.use(
    cors({
        origin:function(origin,callback){
            callback(null,true)
        },
        credentials:true
    })
);

设置特殊的头部信息(例如:header添加token字段[CSRF的一种解决方案])

  1. 增加自定义的头部需要先在后端添加对应的allowedHeadres
  2. alloweHeadres不能直接设置为 *
app.use(
    cors({
        allowedHeaders:'X-Requested-With,Cache-Control,Content-Language,Content-Type,deviceType,appID,subAppID,deviceId,clientVersion,sessionKey'
        origin:function(origin,callback){
            callback(null,true)
        },
        credentials:true
    })
);

当发送复杂请求的时候,不想每次都发options,例如:轮询的一个场景

app.use(
    cors({
        allowedHeaders:'X-Requested-With,Cache-Control,Content-Language,Content-Type,deviceType,appID,subAppID,deviceId,clientVersion,sessionKey'
        origin:function(origin,callback){
            callback(null,true)
        },
        credentials:true,
        maxAge:600 //单位:秒
    })
);

二、Nginx

Nginx需要处理的是对options做处理,以及对其他请求添加对应的头部信息,然后转发给服务器

查看 nginx.conf 的 include xxxx/*.conf,到xxxx目录下面添加 abc.conf,内容如下

server {
    listen abc.com
    location/api {
        add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE';
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Headers' 'appId,Token,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,X_Requested_With,If-Modified-Since,Cache-Control,Content-Type,appId,clientVersion,deviceId,deviceType,subAppId';
        add_header 'Access-Control-Max-Age' 600;
        if ($request_method = 'OPTIONS'){
            return 204;
        }
        proxy_pass  http:127.0.0.1:8080
    }
}

在需要带cookie的情况,除了设置allow-credentials为true之外,allow-origin也不能设置为 * ;对应部分配置如下

location/api {
    ...
    add_header 'Access-Control-Allow-Origin' $http_origin
    add_header 'Access-Control-Allow-Credentials' 'true';
}

三、jsonP(json with padding)

利用网页可以访问跨域静态资源的特性,以script callbackfn的形式来实现跨域数据交互。

如下:

//客户端
function handleCallback(result) {
    console.log(result.message);
}

var jsonp = document.createElement('script');
var ele = document.getElementById('demo');
jsonp.type = 'text/javascript';
jsonp.src = 'http://localhost:8080?callback=handleCallback';
ele.appendChild(jsonp);
ele.removeChild(jsonp);


//node
router.get('/',(req,res)=>{
    let {callback} = req.query;
    let data = {
        test:1111
    };
    if(callback){
        res.type('text/javascipt');
        res.send(`${callback}(${JSON.stringify(data)})`);
    }
    res.send(`${callback}(${JSON.string(data)})`)
});

四、postMessage & Iframe

window.postMessage()方法可以安全地实现跨域通信。通过获取对应窗口的引用,在窗口上调用targetWindow.postmessage的方法发送一个messageEvent消息,接收方通过监听message事件来捕获message

场景:一个运营后台需要预览编辑的问卷在移动端web页面显示的情况,在后台跟移动端web不同域名的情况下,用postMessage来解决数据通信。
代码如下:

//后台
...
async handlePreView(){
    let data =  await this.$form.validateFields(),
        {
            rules=[],
            questions=[]
        } = data;
    if(rules.length < 1 || questions < 1){
        window.message.error('题目和计分规则不能为空');
        return;
    }
    this.$iframe.contentWindow.postMessage(JSON.stringify(data),'*');
}


//移动端
window.addEventListener('message',(event)=>{
    if(event.data !== 'string') return;
    const data = JSON.parse(event.data);
    console.log(data);
},false)

参考资料

  • MDN文档

关于referrer

在接入微信h5支付遇到了referrer为空的问题之后,也看过很多博主整理的相关文档,然后还是自己简单整理一下,加深一下理解。

一、简单介绍

枯燥的概念

Referer 首部包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的。服务端一般使用 Referer 首部识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等

比方说

在一个不火热的旅游景点(很优美没有过度开发的那种)开了个客栈,有一个很无聊的老板,当有游客入住的时候,都会问
场景一:

  • 无聊老板: 我是广东佛山那边的,你是哪边的?
  • 游客:我是肇庆的。
  • 无聊老板:我们是一个省份的,哈哈哈。

场景二:

  • 无聊老板: 我是广东佛山那边的,你是哪边的?
  • 游客:我是从东土大唐而来【心里os,广东人吃胡建人】。
  • 无聊老板:哦哦【心里os,二般游客】

二、正文

referrer 是否发送,在chrome默认的行为里面是会带的(除了是新标签直接访问页面),这样会带来一个问题

  • 可能会暴露用户的浏览历史

那什么情况下不会带这个信息?

  1. 安全降级,例如 https -> http
  2. 协议为file,例如 file://xxx.html
  3. 协议为data,例如 <img src="data:" />
  4. 主动配置

怎么主动配置是否带referer?

配置 referrer-policty

属性值 描述
no-referrer 不发送referrer
no-referrer-when-downgrage 安全等级降级的时候不发,例如https->http
origin 只发送“源”
origin-when-cross-origin 非同源只发送“源”,同源访问发送具体的url
same-origin 同源访问发送具体url
strict-origin 同等安全等级的页面访问才发url
strict-origin-when-cross-origin 同等安全等级且同源的情况下才发送url
unsafe-url 无论是同源请求还是非同源请求都发送完整的url

配置的地方

  1. 通过设置标签,例如,a,area,img,iframe,script,link
例如:
<a href=“xxx.com” referrerpolicy=“origin” />

或者

<a href=“xxx.com” rel=“noreferrer” />  这个时候跳转后,页面http里面的信息的referer就为空
  1. 配置 meta
<meta name=“referrer” conent=“origin” /> 这个时候,referer只会显示origin

有什么应用场景?

1.匿名

有时候匿名者不希望被知道自己的身份,会主动从http报文中删除ua,ip,referer来保证私密性跟匿名性,

2.根据referer去做对应的处理

  • 微信h5支付,在调起支付的时候,就会去验证referer 是否是配置的安全域名
  • b站点用到很多a站点的服务,两个站点的host又是一致的,当有一天两个站点的服务要独立的时候,想要去整理b站点具体依赖到a站点哪些服务是很麻烦的,这个时候直接通过referer去区分就可以直接得知了。

3.防御 CSRF(跨站请求伪造)

  • 描述:跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行
  • 解决方式:检查http headers referer 字段是否同源或者在白名单里面。
  1. 反爬虫,no referrer 或者 非指定地址的referrer 访问图片资源的时候,统一按异常处理。

注意:看到上面其实有referer,也有referrer,referer 是错误的写法,正确的写法是referrer。http header 里面还是referer属性,没有修正。其他的才修正了写法。

http缓存

协商缓存

st=>start: 请求到达
cond1=>condition: 是否已缓存?
cond2=>condition: 是否足够新鲜?
cond3=>condition: 再验证过了
op1=>operation: 与服务器再验证
op2=>operation: 从服务器获取
op3=>operation: 存入缓存
op4=>operation: 对已缓存文档的新鲜度进行更新
e=>end: 提供给客户端

st->cond1(yes)->cond2(yes)->e
cond1(no)->op2
op2->op3(left)->e
cond2(no)->op1(right)->cond3(yes)->e
cond3(no)->op2

ETag

作用

  • 避免“空中碰撞” ETag/if-Match,类似文本编辑后提交前检查是否基于最新文件编辑
  • 缓存未更改的资源 ETag/if-Node-Match

特点

  • http响应资源的特定版本的标识(基于文件内容计算产生的标识)
  • 类似文件指纹

语法

ETag: W/"<etag_value>"
ETag: "<eag_value>"

客户端再次发起资源请求

  • 发送值为ETag的If-None-Match header字段
  • 服务端将客户端发送的ETag与当前版本资源的ETag进行比较
  • 两个值匹配,返回304,告诉客户端资源是新鲜的
  • 两个值不匹配,返回200,并发送新的ETag

Last-Modified、if-Modify-Since、if-ummodified-since

特点

  • 保存服务器认定的资源修改的日期及时间
  • 用于判断接收到的或者存储的资源是否彼此一致

语法

Last-Modified: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT

客户端再次发起资源请求

  • 发送值为last-modified 的 if-modified-since(备注:只可以用在GET 或者 HEAD) header字段
  • 服务端将客户端的if-modified-since的值跟当前资源的last-modified做对比
  • 文件更改日期一致,返回304;不一致,返回200,发送last-modified

if-unmodified-Since:Date
从Date时间起,文件没被修改

用途:断点续传,post,或是非幂等请求

总之一句话,一个是修改了才下载资源,一个是没修改才下载资源

数学与程序

斐波那契数列

用文字來說,就是斐波那契數列由0和1開始,之後的斐波那契數就是由之前的兩數相加而得出

源起

根據高德納(Donald Ervin Knuth)的《計算機程序設計藝術》(The Art of Computer Programming),1150年印度數學家Gopala和金月在研究箱子包裝物件長宽剛好為1和2的可行方法數目時,首先描述這個數列。在西方,最先研究這個數列的人是比薩的李奧納多(義大利人斐波那契Leonardo Fibonacci),他描述兔子生長的數目時用上了這數列:

兔子对的数量就是斐波那契数列
第一個月初有一對剛誕生的兔子
第二個月之後(第三個月初)牠們可以生育
每月每對可生育的兔子會誕生下一對新兔子
兔子永不死去
假設在n月有兔子總共a對,n+1月總共有b對。在n+2月必定總共有a+b對:因為在n+2月的時候,前一月(n+1月)的b對兔子可以存留至第n+2月(在當月屬於新誕生的兔子尚不能生育)。而新生育出的兔子對數等於所有在n月就已存在的a對

```javascript
function rabbitCount(monthNumber = 1){
    let count = [1],
        times = 0;
    while(times <= monthNumber){
        count.unshift(times > 2 ? count[0]+count[1] : count[0]);
        times += 1;
    }
    console.log(count[0]);
}

浅谈《年度盛典》后,对胡里花哨的样式爱恨情仇

年度盛典,作为一年一次的一个活动,除了包含复制的活动逻辑之外,就是“视觉盛宴”了,设计大佬毫不吝啬的用上各种“高级”的设计元素,然后前端死在了css的苦海里边

在一些样式上,如果能用css在实现个人觉得是更优的一个选择,相比于通过图片来呈现

coding

类型判断

参见用于类型判断的方式有:

  1. typeof
  2. instanceof
  3. Object.prototype.toString.call
  4. 特定isXXX,例如: Array.isArray , isNaN

以上方式都可以做数据类型判断;typeof 跟 instanceof 在一些场景下是不能判断具体的类型的,例如 typeof null 是 object;可以覆盖全部类似的是 Object.prototype.toString.call;

function isType(arg,type){
    return `${Object.prototype.toString.call(arg)}`.slice(8,-1).toLocaleLowerCase() === `${type}`.toLocaleLowerCase();
}

const isString = (arg) => isType(arg,'string');
const isArray = (arg) => isType(arg,'array');
const isFunction = (arg) => isType(arg,'function');

啊Link

啊Link

link,很普通的一个标签,在刚开始接触html的时候就知道了可以用来引入css文件,但其实它不仅仅只是用来引入css文件,还有很多很妙的应用场景。

用法

基本

一、链接样式表
基于页面的维护、文档大小、解析优化等,通常我们会把css抽离成一个.css文件,然后通过link引用,如下:

<link rel="stylesheet" href="xxxx.css" />

有时候我们会碰到站点需要提供“多主题选择功能”的需求,那么通常比较直接的就是用 class 去控制,通过切换class的方式来实现;例如:

.hightLight{
    color:#000
    ...
}
.dark{
    color:#fff
    ...
}

二、指定favicon
为站点提供一个icon,如下:

<link rel="icon" href="favicon32.png" />

如果想要更好的展示效果,可以根据不同的设备设置不同的icon,通过 relsizes属性的配置,如下:

<!-- third-generation iPad with high-resolution Retina display: -->
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="favicon144.png">
<!-- iPhone with high-resolution Retina display: -->
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="favicon114.png">
<!-- first- and second-generation iPad: -->
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="favicon72.png">
<!-- non-Retina iPhone, iPod Touch, and Android 2.1+ devices: -->
<link rel="apple-touch-icon-precomposed" href="favicon57.png">
<!-- basic favicon -->
<link rel="icon" href="favicon32.png">

以上这两个用法可能都有用过,但是下面的几种用法,对于页面的优化有很好的效果,却发现很多人原来都没用过,这~~~没看过MDN文档,估计是。

其他用法

在我们的站点内发起一个接口请求的时候,不可避免过程:dns解析->建立tcp链接(https还会有ssl的握手),如果这一过程可以前置,那么是可以节省一定的时间的
一、 dns-prefetch
向浏览器提示,可以提前解析该域名,以便可以更快的获取链接内容。

<link  rel="dns-prefetch"  content="//a.com" />

二、preconnect
向浏览器提供提示,建议浏览器提前打开与链接网站的连接,而不会泄露任何私人信息或下载任何内容,以便在跟随链接时可以更快地获取链接内容。

<link  rel="preconnect"  content="//a.com" />

三、preload,as,importance
当前页面需要使用的资源。
浏览器有预加载扫描器,在打开chrome devTool之后,看到network面板上Priority,标识了浏览器对于资源下载的优先级。
WX20210815-164558@2x.png
在浏览器自己的预加载机制之外,开发者也可以人为指定一些资源预加载,通过preload + as(显式申明资源类型)。

<link ret="preload" content="//a.com/statis/test.js" as="script" />

对于设置了 rel="preload" 的link标签,正确的使用as是必要的,浏览器会根据内容的类型来匹配优先级;
例如:

//html
<img src="xxxx.png" />

WX20210816-111134.png

//html
<img src="xxxx.png" style="display:none;" />

以上两个img的图片都会被下载,但由于display:none的标签在并不会渲染出来的原因,对应这张图片的Priority是low,如下:
WX20210816-111134.png

importance
指定资源下载的优先级,分别是,auto(无设置,根据浏览器设定),hight(优先级较高),low(优先级较低)

chrome在预加载优先级策略如下:
1_BTi3YhvCAYiJYRpjNQft9Q.jpeg

四、prefetch
提示浏览器提前加载链接的资源,因为它**可能**会被用户请求。

//腾讯视频pc web
<link rel="prefetch" href="https://v.qq.com/x/cover/mzc00200xh9313v.html"  />

以上是从腾讯视频站点采集的,把当前热门的电视剧页面加了prefetch,应该是根据站点的热门点击动态调整。

五、crossorigin
指定在加载相关资源时是否必须使用 CORS,启用了CORS的图片可以在 Canvas 中使用
(曾经有遇到过类似问题,那时候的处理方式是后端用同域下的接口获取文件返回文件流)

“anonymous”
会发起一个跨域请求(即包含 Origin: HTTP 头). 但不会发送任何认证信息 (即不发送 cookie, X.509 证书和 HTTP 基本认证信息). 如果服务器没有给出源站凭证 (不设置 Access-Control-Allow-Origin: HTTP 头), 资源就会被污染并限制使用.

"use-credentials"
会发起一个带有认证信息 (发送 cookie, X.509 证书和 HTTP 基本认证信息) 的跨域请求 (即包含 Origin: HTTP 头). 如果服务器没有给出源站凭证 (不设置 Access-Control-Allow-Origin: HTTP 头), 资源就会被污染并限制使用.

当不设置此属性时, 资源将会不使用 CORS 加载

//facebook
<link href="https://static.xx.fbcdn.net/rsrc.php/v3ipIp4/yX/l/zh_CN/suwOLbpGGk-.js?_nc_x=Ij3Wp8lg5Kz" rel="preload" as="script" crossorigin="anonymous">

从fb的站点下面,可以看到大量的link标签的应用。
六、alternate + hreflang
alternate 谷歌翻译为”备用“;在实际的使用场景表现也确实是如此;
例如,下图是googlePlay页面的部分dom;通过 alternate + href + hreflang 来实现多语言seo的优化;
WX20210817-112521.png

官方例子则是通过 alternate + title 来实现网页多样式显示,如下:

//用户可以在浏览器菜单 "查看>页面样式" 来选择网页的样式。通过这一办法,可以用多种样式浏览网页

<link href="default.css" rel="stylesheet" title="Default Style">
<link href="fancy.css" rel="alternate stylesheet" title="Fancy">
<link href="basic.css" rel="alternate stylesheet" title="Basic">

webpackPlugin

通过webpack的插件的方式实现部分常规优化点自动化。

其他

XSS/CSRF/CSP/ClickJacking

XSS(跨网站指令码 Cross-site scription)

概述

利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令到网页,使用户加载并执行攻击装恶意制造的网页程序;攻击成功后,可能得到更高的权限、私密网页内容、会话和cookie等各种内容

攻击方式

  • 反射型:攻击者构成一个带有恶意代码的url链接诱导正常用户点击,服务器接收到这个url对应的请求读取出其中的参数然后没有做过滤就凭借到html页面发送给浏览器,浏览器解析。
  • 存储型:攻击者将带有恶意代码的内容发送给服务器,服务器没做任何过滤将数据存储到数据库中,下次再请求这个页面的时候服务器直接从数据库取出相关内容拼接到html上面,浏览器解析。
  • dom型:dom型xss工具是js获取到攻击者输入的内容并插入到html中。

分析

方式:通过注入恶意内容进行攻击
目的:获取用户隐私内容或者执行恶意操作

案例

一、通过url注入

http://test.com/static/shareLink/index.html?shareUserName=<script>alert(document.cookie)</script>
<html>
<head>
<script>
let $referer = document.querySelectorAll('.referer');
referer.innerHtml = shareUserName;
</script>
</head>
<body>
    <p>
        <span class='referer'></span>分享给你
    </p>
</body>
</html>

二、通过npm包注入
例如:《高达 800 万次下载量的 npm 包被黑客篡改了代码,你的设备或正成为挖矿机》;

event-stream,是一个用于处理 Node.js 流数据的 JavaScript npm 包, event-stream 突然被发现包含一个名为 flatmap-stream 的依赖项,而这个依赖项被植入了窃取比特币的后门,这意味着使用到该模块的开发者们,你们的设备或许早已在自己不知情的情况下变成了挖矿机。

三、通过表单注入
通过表单提交永久注入,在其他用户,在加载到该内容直接执行了恶意脚本

//Form
<input userName value="<script>alert(documemt.cookie)</sctipt>" />

//Html
<p>《安全防御》---作者:<script>alert(documemt.cookie)</sctipt></p>

四、SQL注入
当系统的用户登录校验是通过“SELECT * FROM accounts WHERE username='admin' and pasword='password' ”这类显式的sql进行校验;

//用户名
<input name="username" />

//密码
<input name="psw" type="password" />

当用户在以上 username 输入框输入 "admin' and 1=1 /*" , 系统的校验SQL 语句是这样的

SELECT * FROM accounts WHERE username='admin' and 1=1 /*' and password = ''

因为 /*后的语句直接被当成注释忽略,用户直接登录成功了

防御手段

一、特殊符号转义

const escapeHTML = (str)=>{
    return str.replace(/[&<>'"]/g,(tag)=>(
        {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            "'": '&#39;',
            '"': '&quot;'
        }[tag] || tag
    ));
}

二、在引用第三方包之前,提前评估风险,尽量使用比较多人使用的npm包,npm包的版本加上lock

三、在设计应用程序时,完全使用完全参数化(Parameterized Query)来设计资料存取功能

set @userName := xxx;
set @passowrd := xxx; 

UPDATE myTable SET c1 = @c1, c2 = @c2, c3 = @c3 WHERE c4 = @c4

SELECT * FROM accounts WHERE username=@userName and password = @passowrd

H5支付踩坑记

在工作中对于h5支付这个,遇到的问题很多,所以想记录一下,以后说不定就忘记了,没对接过的,看一下也可以避免很多坑。

背景

因需要更灵活的运营场景,在app内需要接入第三方(微信、支付宝)h5支付

基本信息文档

微信h5支付开发及常见问题:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4

项目

基本的接入方式不做过多的说明,下面以项目的维度简单说明一下在使用h5支付的过程中碰到的问题,以及处理方式;

星座牌小游戏

概述:

星座小游戏是一个卡片类抽金币的小游戏,用户通过选择对应的牌来抽取大于支付金额等值的金币;

基本流程:

luckdraw.png

关键逻辑及处理方式

一、星座牌小游戏调起微信h5支付

// 打开微信支付
let payParams = JSON.parse(ret.data.payParams)
let openWxUrl = payParams.payUrl + "&redirect_url=" + encodeURIComponent(redirectUrl)
let hideFrame = document.createElement('iframe')
hideFrame.setAttribute('src', openWxUrl);
hideFrame.setAttribute('sandbox','allow-scripts allow-top-navigation allow-same-origin')
document.body.appendChild(hideFrame)
/*
这里更加优雅的可以用iframe onload 去处理,因为用户加载iframe的时间是受网络环境影响,直接2s后移除不可控
setTimeout(function() {
   hideFrame.parentNode.removeChild(hideFrame);
}, 2000);
*/
hideFrame.onload = function(){
    //其他逻辑
    setTimeout(function() {
       hideFrame.parentNode.removeChild(hideFrame);
    }, 100);
}

上面的代码,有几个处逻辑处理,对应的,通过下面的几个问题解释;

  1. 为什么通过iframe的形式加载payUrl?
    由星座牌小游戏的交互决定,因为用户在支付完之后,还需要在当前页面开奖,因此,通过iframe的形式处理,还能解决微信h5支付对于payUrl 加载的referer验证问题;

  2. iframe 为什么要设置sandbox?,为什么要设定setTimeout去移除iframe?

    • 在ios13发布的时候,ios出现了问题,微信h5支付没有调起成功;
    • app在安卓的某个版本之后也出现了同样的问题,对应那个版本的app,app打包的安卓sdk版本做了更新

对应的异常log如下

message:Uncaught SecurityError: Failed to set the 'href' property on 'Location': The current window does not have permission to navigate the target frame to 'weixin://wap/pay?prepayid%3Dwx241530497040689dd1394a4f1296524900&package=3510274145&noncestr=1563953450&sign=7867a2dd7feb974db9285725cb2822d0'.
从log看到的代码error,其实不是业务本身的代码,而是payUrl加载后,支付页里面的"top.location.href"异常,关键代码如下
//payUrl
                var is_postmsg="";
                if(is_postmsg=="1")
                {
                    parent.postMessage(JSON.stringify({
                        action : "send_deeplink",
                        data : {
                            deeplink : "weixin://wap/pay?prepayid%3Dwx28141344091494d0c83d3c3cd472e50000&package=3738825662&noncestr=1609136025&sign=02d138bef13beb9222479014a4a4ea85"
                        }
                    }), "");
                }
                else
                {
                    var url="weixin://wap/pay?prepayid%3Dwx28141344091494d0c83d3c3cd472e50000&package=3738825662&noncestr=1609136025&sign=02d138bef13beb9222479014a4a4ea85";
                    var redirect_url="https://ulink.com/ulink/lucky/index.html";
                    top.location.href=url;

                    if(redirect_url)
                    {
                        setTimeout(
                            function(){
                                top.location.href=redirect_url;
                            },
                            5000
                        );
                    }
                    else
                    {
                        setTimeout(
                            function(){
                                window.history.back();
                            },
                            5000);
                    }
                }

从上面的代码块可以看到payUrl调起微信的逻辑

  1. 调起微信走的else的逻辑(这里的postmsg 的逻辑暂时未从微信官方文档内找到对于的配置方式),通过 top.loaction.href= url 的形式 加载 scheme调起;

  2. 如果有redirect_url,会设置个定时器, 5秒后重定向到redirect_url --- 这也是为什么星座牌小游戏需要添加一个remove iframe的逻辑的原因,

整体梳理下来,问题的原因就是pp在用新的android sdk打包之后,webview的内核版本的提升,对应的内容安全策略(csp)调整(默认设置调整),阻止了payUrl通过top的方式直接访问父页面的api;那么这个问题是通过iframe设置sandbox属性解决:

/* sandbox
allow-forms 允许进行提交表单
allow-scripts 运行执行脚本
allow-same-origin 允许同域请求,比如ajax,storage
allow-top-navigation 允许iframe能够主导window.top进行页面跳转
allow-popups 允许iframe中弹出新窗口,比如,window.open,target=”_blank”
allow-pointer-lock 在iframe中可以锁定鼠标,主要和鼠标锁定有关
*/
hideFrame.setAttribute('sandbox','allow-scripts allow-top-navigation allow-same-origin')
 

3、redirectUrl是回调地址,微信是怎么跳回指定的地址的?redirectUrl为什么配置ulink的地址?
微信处理redirectUrl的逻辑

if(redirect_url)
{
   setTimeout(
   function(){
      top.location.href=redirect_url;
   },
   5000);
}
else
{
  setTimeout(
     function(){
       window.history.back();
      },
  5000);
}
redirectUrl为什么配置ulink的地址?

android在跳到微信支付完成后,点击支付成功页面的“完成”按钮,会回到调起微信支付的app,ios点击完成不会做这部分操作,在有配置redirectUrl的情况下会用safari打开redirectUrl;且redirectUrl也有域名的验证;

配置成ulink的地址,是为了解决ios在支付完成后回到app内的处理方式;

注:ios也有不能直接回到app的情况,而是通过safari打开了ulink的页面,这种情况,经过跟客户端同事的一同排查的结果是因为用户在安装app的时候,没有下载到apple-app-site-association这个文件导致;

app充值(内嵌、外部)

概述:

内嵌页h5充值页面,提供方便用户充值的页面,并且支持微信支付,支付宝支付,对应app内的充值;

基本逻辑:

同星座牌小游戏类似,不同的地方是开奖变成了用户余额查询;

一、微信h5支付

避免了星座牌小游戏上喷到的问题之后,暂时(这个在子app的充值弹窗上又遇到了新的问题)没其他的问题出现;
加载payUrl后交互时序图(非官方):
wxpayUrl.png

二、支付宝h5支付(支付宝h5支付有两种方式:1. 纯h5页面支付;2.h5调起支付宝app支付),以下为h5调起支付宝app支付的流程

支付宝h5支付,返回的不是payUrl,而是一段formDomString;

提交payForm后交互时序图(非官方):
aliPayForm.png

<!-- 支付宝h5支付,接口返回的form信息 -->
<form name=\"punchout_form\" method=\"post\" action=\"https://openapi.alipay.com/gateway.do?charset=UTF-8&method=alipay.trade.wap.pay&sign=HfcRgFeT%2FSVj1soSQrBQYCV%2BaoQzrBVupczUmmjM0sQ2FqXlFHMqOti4EexmhSh3Ap%2FRAAG8MXlo%2FTbzVquR59bXe3deuTXc30S5cgsV9l00jaKPOKXdSfJah2r%2FR5onafKys9caXLaaQmVwtrSrWr5hMFz%2FmtfZvZWwch%2FFvJuVS0wlGT128GBG0KSiUue0g2Bs%2BVg%2B3WKhIiQLCBMKB7BuuyFCnvwnpjeLiGafjIYr6CNBn83uzac1QX9OBuzp91EVLGbBSwAFyyxALhporUh4pDe27SqJbwg15kQd6tDp2f7423M6AoQGkEDMdzaBWRTu2UrMenzaqDOpFpilHA%3D%3D&return_url=https%3A%2F%2Fapp.test.com%2Fstatic%2Fh5Conversion%2Findex.html&notify_url=https%3A%2F%2Fapp.com%2Fcallback%2Fppywforkylin%2Falipay%2Falipay%2F5103092247759423283&version=1.0&app_id=2021001145660238&sign_type=RSA2&timestamp=2020-12-28+15%3A28%3A39&alipay_sdk=alipay-sdk-java-3.4.49.ALL&format=json\">\n<input type=\"hidden\" name=\"biz_content\" value=\"{"body":"10金币","out_trade_no":"1231231233123123123","product_code":"QUICK_WAP_WAY","subject":"10金币","timeout_express":"2m","total_amount":"1"}\">\n<inp

1、以上form代码段直接通过innerHtml插入页面还是不行的,script内的submit并不会被执行
HTML 5 中指定不执行由 innerHTML 插入的 <script> 标签,解决方式如下:

//业务处理
$iframe.innerHtml = payForm;
//HTML 5 中指定不执行由 innerHTML 插入的 <script> 标签。
$iframe.qeuerySelector('form').submit(); 

提交Form表单后,支付宝部分代码
支付宝scheme唤起逻辑

// 安卓走iframe方式唤起
        if (ua.indexOf('android')>-1 && !noIntentTest) {
            canIntent = false;
        }

        /**
         * open client
         */
        _AP.open = function (params) {
            if (!domLoaded && (ua.indexOf('360 aphone')>-1 || canIntent)) {
                var arg = arguments;
                delayToRun = function () {
                    _AP.open.apply(null, arg);
                    delayToRun = null;
                };
                return;
            }

            if (locked) {
                return;
            }
            locked = true;

            var o;
            // START::  回跳 scheme 处理
            var iosScheme, androidScheme, backScheme;
                        if (backScheme) {
                iosScheme = backScheme.ios;
                androidScheme = backScheme.android;
                try {
                  window.tracker.log({
                    code: 11,
                    msg: 'scheme来源: '+ JSON.stringify(backScheme),
                    sampleRate: 1,
                  });
                }catch(e){
                  console.warn('scheme来源获取错误:', e)
                }
            }

            if (typeof params === 'object') {
                if (iosScheme) {
                    params.h5FromAppUrlScheme = iosScheme;
                    params.sourceSceneType = 'h5Route';
                }
                o = {
                    'ios': encodeURIComponent(JSON.stringify(params)),
                    'android': encodeURIComponent(params.dataString)
                };
                if (androidScheme) {
                    o.android = o.android + '&sourceSceneType=h5Route&h5FromAppUrlScheme=' + androidScheme
                }
            } else {
                console.error('params error, pls use JSON format!')
            }
            // END

            // params fault tolerance
            if (typeof o.ios !== 'string') {
                o.ios = '';
            } else if(typeof o.android !== 'string') {
                o.android = '';
            }

            // nonsupport Android intent
            if (!canIntent) {
                if(isAndroid) {
                    var alipaysUrl = 'alipays://platformapi/startApp?appId=20000125&orderSuffix=' + o.android +'#Intent;scheme=alipays;package=com.eg.android.AlipayGphone;end';
                }
                //fix for iOS QQ browser
                else if (ua.indexOf('mqqbrowser') > -1) {
                    var alipaysUrl = 'alipay://alipayclient/?' + o.android;
                }
                else {
                    var alipaysUrl = 'alipay://alipayclient/?' + o.ios;
                }
                //FIXME: 直接判断ios,不判断os版本号
                if ( ua.indexOf('qq/') > -1 || ( ua.indexOf('safari') > -1 && ua.indexOf('os 9_') > -1 ) || ( ua.indexOf('safari') > -1 && ua.indexOf('os 10_') > -1 ) || ( ua.indexOf('safari') > -1 && ua.indexOf('os 11_') > -1 ) || ( ua.indexOf('safari') > -1 && ua.indexOf('os 12_') > -1 ) || ( ua.indexOf('safari') > -1 && ua.indexOf('os 13_') > -1 ) || ( ua.indexOf('safari') > -1 && ua.indexOf('os 14_') > -1 ) ) {
                    var openSchemeLink = document.getElementById('openSchemeLink');
                    if (!openSchemeLink) {
                        openSchemeLink = document.createElement('a');
                        openSchemeLink.id = 'openSchemeLink';
                        openSchemeLink.style.display = 'none';
                        document.body.appendChild(openSchemeLink);
                    }

                    //openSchemeLink.href = alipaysUrl;
                    // oppo浏览器兼容写法
                    openSchemeLink.onclick = function() {
                        window.location.href = alipaysUrl;
                    };

                    // trigger click
                    openSchemeLink.dispatchEvent(customClickEvent());
                }
                else {
                    var ifr = document.createElement('iframe');
                    ifr.src = alipaysUrl;
                    ifr.style.display = 'none';
                    document.body.appendChild(ifr);
                }
                $('.J-startapp').attr('href', alipaysUrl);
            }
            //support Android intent
            else {
                var packageKey = 'AlipayGphone';
                var intentUrl = 'alipays://platformapi/startApp?appId=20000125&orderSuffix='+o.android+'#Intent;scheme=alipays;package=com.eg.android.'+ packageKey +';end';

                var openIntentLink = document.getElementById('openIntentLink');
                if (!openIntentLink) {
                    openIntentLink = document.createElement('a');
                    openIntentLink.id = 'openIntentLink';
                    openIntentLink.style.display = 'none';
                    document.body.appendChild(openIntentLink);
                }

                //openIntentLink.href = intentUrl;
                // oppo浏览器兼容写法
                openIntentLink.onclick = function() {
                    window.location.href = intentUrl;
                };

                // trigger click
                openIntentLink.dispatchEvent(customClickEvent());
            }

            setTimeout(function () {
                locked = false;
            }, 2500)

支付宝结果轮询,以及returnUrl 逻辑

//
//轮询
        var payquery = function () {
            if(stopQuery) { return }
            var argumentsPayquery = arguments;
            Zepto.ajax({
                type: 'post',
                url: '/h5/h5RoutePayResultQuery.json?h5_route_token=RZ42FugnN5SrDYUBQr3vTTeQg3magDmobilecashierRZ42&need_invoke_app=true',   /*/h5/h5RoutePayResultQuery.json?h5_route_token=*/
                data:{
                    '_input_charset': 'utf-8',
                    'params': $('input[name=params]').val(),
                    'session': 'RZ42FugnN5SrDYUBQr3vTTeQg3magDmobilecashierRZ42'
                },
                timeout: 30000,
                dataType: 'json',
                success: function (data) {
                    //已唤起支付宝客户端
                    if(data.data.invokeAlipay && time){
                        time = 0;
                    }

                    //成功
                    if(data.control_type == 'pay_success') {
                        if(data.data && data.data.returnUrl && data.data.returnUrl != '') {
                            window.location.replace(decodeURIComponent(data.data.returnUrl));
                        }
                    }
                    //继续轮询
                    else if (data.control_type == 'h5_route_need_pay_query') {
                        if(!data.data.stopQuery) {
                            setTimeout(function(){
                                argumentsPayquery.callee();
                            }, data.data.dismisstime);
                        }
                    }
                }
            });
        }
 

子app-充值弹窗

概述:

app内需要有一个h5充值的半屏弹窗,只接入微信h5支付

问题:
1、iphone用户,在h5调起微信app支付界面后,取消或者完成支付后没有返回app
原因:客户端的ua问题

1、子app与主app的ua比较
子app:

userAgent%20/3.9.0_build119317%20NetType/WiFi%20Language/zh-Hans-CN),

主app:

Mozilla/5.0%20(iPhone;%20CPU%20iPhone%20OS%2012_4_1%20like%20Mac%20OS%20X)%20AppleWebKit/605.1.15%20(KHTML,%20like%20Gecko)%20Mobile/15E148%20/1.8.8_build132540%20NetType/WiFi%20Language/zh-Hans-CN)

调试:通过whistle调整userAgent为添加mobile标识后,测试正常(推测微信支付界面点击完成或者取消用safari访问redirectUrl的逻辑中,有ua的判断逻辑)

表现:子app调起微信支付后,点击取消,不执行回调url

处理方案:下个版本需要更新ios webview的ua

备注:这边尝试用扫一扫打开星座牌小游戏,结果是一样的,都是不能回到app(这边没找到星座牌小游戏的入口,只能通过扫一扫测试,历史遗留问题)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.