Linux网络调试 常用命令和分析工具 排查方法

查看/配置网络

ifconfig 查看IP地址

https://wangchujiang.com/linux-command/c/ifconfig.html

enx00e04cf0a10c: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.5  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 2408:8207:305c:1c30:b48f:56c7:81bb:bb9  prefixlen 64  scopeid 0x0<global>
        inet6 fe80::2c1b:1b85:35cd:9642  prefixlen 64  scopeid 0x20<link>
        inet6 2408:8207:305c:1c30:e49b:195a:eeaa:1e44  prefixlen 64  scopeid 0x0<global>
        ether 00:e0:4c:f0:a1:0c  txqueuelen 1000  (Ethernet)
        RX packets 29715  bytes 31484642 (31.4 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 22107  bytes 3794564 (3.7 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 4003  bytes 2920205 (2.9 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 4003  bytes 2920205 (2.9 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
 
wlp4s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.0.111  netmask 255.255.255.0  broadcast 192.168.0.255
        inet6 fe80::6e92:514f:2d68:1da1  prefixlen 64  scopeid 0x20<link>
        ether 14:4f:8a:60:f2:2a  txqueuelen 1000  (Ethernet)
        RX packets 106  bytes 9074 (9.0 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 129  bytes 19965 (19.9 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

e开头的一般是有线网卡 例eth

lo 全称是 loopback,又称环回接口,往往会被分配到 127.0.0.1 这个地址。这个地址用于本机通信,经过内核处理后直接返回,不会在任何网络中出现

wlp开头表示无线网卡

flags = 4163 没有搜到什么意思

UP表示接口启用

BROADCAST 表示主机支持广播

RUNNING 表示接口正在工作

MULTICAST 主机支持多播

MTU:1500 最大传输单元 1500字节 百科

inet 网卡的IP地址

netmask 网络掩码

broadcast 广播地址

inet6 fe80::2aa:bbff:fecc:ddee prefixlen 64 scopeid 0x20<link> IPv6地址

ether 00:e0:4c:f0:a1:0c 连接类型:Ethernet (以太网) HWaddr (硬件mac地址)

txqueuelen (网卡设置的传送队列长度)

RX packets 接收时,正确的数据包数

RX bytes 接收的数据量

RX errors 接收时,产生错误的数据包数

RX overruns 接收时,由于速度过快而丢失的数据包数

RX dropped 接收时,丢弃的数据包数

RX frame 接收时,发生frame错误而丢失的数据包数

TX carrier 发送时,发生carrier错误而丢失的数据包数

TX collisions 冲突信息包的数目

mii-tool 配置网络设备协商方式的工具

https://wangchujiang.com/linux-command/c/mii-tool.html

是用于查看、管理介质的网络接口的状态,有时网卡需要配置协商方式,比如10/100/1000M的网卡半双工、全双工、自动协商的配置。但大多数的网络设备是不用我们来修改协商,因为大多数网络设置接入的时候,都采用自动协商来解决相互通信的问题。不过自动协商也不是万能的,有时也会出现错误,比如丢包率比较高,这时就要我们来指定网卡的协商方式。mii-tool就是能指定网卡的协商方式。下面我们说一说mii-tool的用法。

sudo mii-tool -v enx00e04cf0a10c
enx00e04cf0a10c: negotiated 100baseTx-FD, link ok
product info: vendor 00:07:32, model 0 rev 0
basic mode: autonegotiation enabled
basic status: autonegotiation complete, link ok
capabilities: 1000baseT-FD 100baseTx-FD 100baseTx-HD 10baseT-FD 10baseT-HD
advertising: 1000baseT-FD 100baseTx-FD 100baseTx-HD 10baseT-FD 10baseT-HD flow-control
link partner: 100baseTx-FD 100baseTx-HD 10baseT-FD 10baseT-HD

link ok 表示网线连接ok

basic mode: autonegotiation enabled 启用自动协商

route 查看网关

https://wangchujiang.com/linux-command/c/route.html

显示并设置Linux中静态路由表

常用方式 route -n 不执行DNS反向查找,直接显示数字形式的IP地址

route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.1.1     0.0.0.0         UG    100    0        0 enx00e04cf0a10c
0.0.0.0         192.168.0.1     0.0.0.0         UG    600    0        0 wlp4s0
169.254.0.0     0.0.0.0         255.255.0.0     U     1000   0        0 wlp4s0
192.168.0.0     0.0.0.0         255.255.255.0   U     600    0        0 wlp4s0
192.168.1.0     0.0.0.0         255.255.255.0   U     100    0        0 enx00e04cf0a10c

ip 网络配置工具

https://wangchujiang.com/linux-command/c/ip.html

用来显示或操纵Linux主机的路由、网络设备、策略路由和隧道

支持信息的object

OBJECT := { link | address | addrlabel | route | rule | neigh | ntable | tunnel | tuntap | maddress | mroute | mrule | monitor | xfrm | netns | l2tp | macsec | tcp_metrics | token }

nc 全称netcat

用于设置路由器。它能通过 TCP 和 UDP 在网络中读写数据。通过与其他工具结合和重定向,你可以在脚本中以多种方式使用它。

https://wangchujiang.com/linux-command/c/nc.html

查看端口实例

nc -vz xiaoyuapi.zcoming.com 443 -w2
Connection to xiaoyuapi.zcoming.com 443 port [tcp/https] succeeded!

故障排除

ping 测试主机之间网络的连通性

https://wangchujiang.com/linux-command/c/ping.html

执行ping指令会使用ICMP传输协议,发出要求回应的信息,若远端主机的网络功能没有问题,就会回应该信息,因而得知该主机运作正常

traceroute 显示数据包到主机间的路径

https://wangchujiang.com/linux-command/c/traceroute.html

用于追踪数据包在网络上的传输时的全部路径,它默认发送的数据包大小是40字节。

通过traceroute我们可以知道信息从你的计算机到互联网另一端的主机是走的什么路径。当然每次数据包由某一同样的出发点(source)到达某一同样的目的地(destination)走的路径可能会不一样,但基本上来说大部分时候所走的路由是相同的。

traceroute通过发送小的数据包到目的设备直到其返回,来测量其需要多长时间。一条路径上的每个设备traceroute要测3次。输出结果中包括每次测试的时间(ms)和设备的名称(如有的话)及其ip地址。

traceroute www.58.com
traceroute to www.58.com (211.151.111.30), 30 hops max, 40 byte packets
 1  unknown (192.168.2.1)  3.453 ms  3.801 ms  3.937 ms
 2  221.6.45.33 (221.6.45.33)  7.768 ms  7.816 ms  7.840 ms
 3  221.6.0.233 (221.6.0.233)  13.784 ms  13.827 ms 221.6.9.81 (221.6.9.81)  9.758 ms
 4  221.6.2.169 (221.6.2.169)  11.777 ms 122.96.66.13 (122.96.66.13)  34.952 ms 221.6.2.53 (221.6.2.53)  41.372 ms
 5  219.158.96.149 (219.158.96.149)  39.167 ms  39.210 ms  39.238 ms
 6  123.126.0.194 (123.126.0.194)  37.270 ms 123.126.0.66 (123.126.0.66)  37.163 ms  37.441 ms
 7  124.65.57.26 (124.65.57.26)  42.787 ms  42.799 ms  42.809 ms
 8  61.148.146.210 (61.148.146.210)  30.176 ms 61.148.154.98 (61.148.154.98)  32.613 ms  32.675 ms
 9  202.106.42.102 (202.106.42.102)  44.563 ms  44.600 ms  44.627 ms
10  210.77.139.150 (210.77.139.150)  53.302 ms  53.233 ms  53.032 ms
11  211.151.104.6 (211.151.104.6)  39.585 ms  39.502 ms  39.598 ms
12  211.151.111.30 (211.151.111.30)  35.161 ms  35.938 ms  36.005 ms

记录按序列号从1开始,每个纪录就是一跳 ,每跳表示一个网关,我们看到每行有三个时间,单位是ms,其实就是-q的默认参数。探测数据包向每个网关发送三个数据包后,网关响应后返回的时间;如果用traceroute -q 4 www.58.com,表示向每个网关发送4个数据包。

有时我们traceroute一台主机时,会看到有一些行是以星号表示的。出现这样的情况,可能是防火墙封掉了ICMP的返回信息,所以我们得不到什么相关的数据包返回数据。

有时我们在某一网关处延时比较长,有可能是某台网关比较阻塞,也可能是物理设备本身的原因。当然如果某台DNS出现问题时,不能解析主机名、域名时,也会 有延时长的现象;您可以加-n参数来避免DNS解析,以IP格式输出数据。

如果在局域网中的不同网段之间,我们可以通过traceroute 来排查问题所在,是主机的问题还是网关的问题。如果我们通过远程来访问某台服务器遇到问题时,我们用到traceroute 追踪数据包所经过的网关,提交IDC服务商,也有助于解决问题;但目前看来在国内解决这样的问题是比较困难的,就是我们发现问题所在,IDC服务商也不可能帮助我们解决。

mtr My traceroute

https://www.cnblogs.com/alexyuyu/articles/2811346.html

Linux 网络诊断工具MTR(My traceroute) 结合了ping跟tracert的一个工具

首先看最后一行,目标服务器有没有丢包,有的话再往前分析。

测试指定端口实例

sudo mtr -P 443 -i 0.5 -rwc 50 xiaoyuapi.zcoming.com

nslookup

是常用域名查询工具,就是查DNS信息用的命令。

https://wangchujiang.com/linux-command/c/nslookup.html

非交互模式直接加域名

nslookup xiaoyuapi.zcoming.com
Server:		127.0.0.53
Address:	127.0.0.53#53

Non-authoritative answer:
Name:	xiaoyuapi.zcoming.com
Address: 47.92.203.163

telnet 登录远程主机和管理(测试ip端口是否连通)

https://wangchujiang.com/linux-command/c/telnet.html

telnet因为采用明文传送报文,安全性不好,很多Linux服务器都不开放telnet服务,而改用更安全的ssh方式了。但仍然有很多别的系统可能采用了telnet方式来提供远程登录,因此弄清楚telnet客户端的使用方式仍是很有必要的

检测端口,如下表示端口正常

telnet xiaoyuapi.zcoming.com 443
Trying 47.92.203.163...
Connected to xiaoyuapi.zcoming.com.
Escape character is '^]'.

tcpdump

https://wangchujiang.com/linux-command/c/tcpdump.html

 是一款抓包,嗅探器工具,它可以打印所有经过网络接口的数据包的头信息,也可以使用-w选项将数据包保存到文件中,方便以后分析

netstat 查看Linux中网络系统状态信息

https://wangchujiang.com/linux-command/c/netstat.html

可以查看网络协议tcp/udp ip地址监听状态 程序进程id等

netstat -ltnp //查看监听中的进程

ss socket统计信息

https://wangchujiang.com/linux-command/c/ss.html

比 netstat 好用的socket统计信息,iproute2 包附带的另一个工具,允许你查询 socket 的有关统计信息

命令安装

有些发行版可能没有这些命令需要自己安装

net-tools包含以下常用的命令:apt-get install net-tools

  • ifconfig:显示和配置网络接口的信息。
  • netstat:显示网络连接、路由表和网络统计信息。
  • arp:显示和操作ARP缓存。
  • route:显示和操作IP路由表。
  • hostname:显示或设置系统的主机名。

iproute2包含以下常用的命令: apt-get install iproute2

  • ip:用于配置和管理网络接口、路由表、策略路由、隧道、桥接等各种网络参数。
  • ss:显示套接字统计信息,包括网络连接、监听端口、进程关联的套接字等。
  • bridge:用于配置和管理网络桥接。
  • tc:配置和管理流量控制策略。
  • ipset:管理iptables使用的IP集合。
  • nstat:显示网络统计信息。
  • rtacct:显示路由表统计信息。

traceroute命令需要 apt-get install traceroute

网络监控工具

nethogs https://wangchujiang.com/linux-command/c/nethogs.html

//安装
sudo apt install nethogs
sudo yum install nethogs

//使用
sudo nethogs -d 1
sudo nethogs eth0 -d 1
GUI

网络问题排查思路

1.查看服务器服务是否正常,你的网络能否正常访问

当用户反馈APP或网站无法访问或卡顿的时候,首先用你的网络测试一下能否正常访问,

然后排查服务器是否有问题CPU,内存,网络,指标是否正常,进程数量,TCP连接数量 平均负载

各种日志,负载均衡日志,nginx日志,程序日志

2.客户端排查,收集详细的用户反馈

先让用户访问一些常用的网站和app,比如百度等,排除是否是用户的网络问题

收集问题用户数据,手机型号, 操作系统,网络类型(3G,4G,wifi) 运营商(移动,联通,电信),点击哪个模块哪个功能出现的网络故障提示,什么时间点,最好能录屏或者截个图。

或者在程序中增加功能,网络不好的时候弹窗提示用户反馈信息,用户点击反馈按钮收集客户端的ip操作系统型号,上报给服务器并记录下来,通过ip可以获取网络运营商的信息。

3.通过第三方测试平台,模拟用户的网络环境

站长之家测速工具

云服务提供商也用类似站点监控的工具 通过以上工具测试否为运营商问题

4.APP(移动测试)远程真机调试

参考

手机远程真机调试平台汇总
有哪些好用的App云测试平台

目前对移动App的测试主要指的是下面几部分:

兼容性测试——App对不同手机、操作系统版本的兼容性测试,包括安装、启动、卸载等。
功能测试——遍历应用的每一个角落,查看应用的功能、逻辑是否正常,完整。
性能测试——应用的性能怎样,如启动时间、反应时间、CPU占用率,内存占用率等。
稳定性测试——在一定时间内对App进行持续地测试,测试App运行的稳定性。
网络场景测试——测试不同网络环境中App的运行状况,如2G&3G,弱网络等。

Testin:Testin云测|全球领先的App及手游测试平台

腾讯优测:优测网-让测试更简单

贯众云测试:贯众云测试

百度MTC:百度移动云测试中心

阿里MQC:MQC 阿里移动质量中心

参考


ORM扣库存导致的BUG总结

前一段时间,以前一个项目的积分兑换功能出现了库存超扣现象,排查问题时,一看更新时没有加锁,于是以为是锁的问题,加锁之后没有仔细推敲就汇报bug修复完成.

//初始代码
DB::transaction(function () use ($request, $user_model) {
 $goods_model = Goods::find($request->id);
 if ($goods_model->stock <= 0 || $goods_model->stock < $request->number) {
          throw new \Exception('库存不足');
         }
  //扣库存
 $goods_model->stock-=$request->number;
 $goods_model->save();
 ...
});

//第一次修复BUG,增加悲观锁 for update
DB::transaction(function () use ($request, $user_model) {
 $goods_model = Goods::lockForUpdate()->find($request->id);
 if ($goods_model->stock <= 0 || $goods_model->stock < $request->number) {
          throw new \Exception('库存不足');
         }
  //扣库存
 $goods_model->stock-=$request->number;
 $goods_model->save();
 ...
});

昨天接的报告,库存超扣BUG又出现了,立即查看Binlog 日志排查sql执行记录

看日志,同一时刻,有两个线程(386609669,370064926)更新库存,线程386609669 先将库存更新为2 ,线程370064926随后又将库存更新为9 ,并且响应时间760ms ,猜想是数据库响应问题,正考虑其它解决方案,队列或限流等.

看日志nginx,发现在抢购兑换时间内只有几十的并发,看了一下RDS的资源统计使用率不高,感觉悲观锁可以应付的来,不是性能问题.

继续看binlog 日志,发现库存被 线程370064926更新为9之后,又被其它线程386850322更新为3,继续往后看发现同一时间内有好多线程在同时更新库存而且更新库存数量不相同.

百思不得其解,网上Google Mysql高并发扣库存方案,其中有一只乐观锁方案,提到在事物中更新库存时,要比对程序中的库存数量和mysql中的库存数量,突然间恍然大悟,仔细一看代码.果然,犯了一个相当愚蠢的错误.

DB::transaction(function () use ($request, $user_model) {
 $goods_model = Goods::lockForUpdate()->find($request->id);
 if ($goods_model->stock <= 0 || $goods_model->stock < $request->number) {
          throw new \Exception('库存不足');
         }
  //扣库存
 $goods_model->stock-=$request->number; 标注1
 $goods_model->save();
 
 ...
});

问题分析

标注1

更新使用的是程序模型中的库存数量 – 兑换数量

假设stock为 9 number 为 2
上述写法生成的sql 为 update goods set stock = 7 where id = ?
在高并发场景下这么写显然是非常错误的. 假设商品的初始库存为10,ABC三个用户同一时刻兑换商品,ABC读取到程序模型中的库存数量 $goods_model->stock 都是10 ,由于用了悲观锁for update(这不是重点,不用悲观锁也会出现这种情况) 更新语句变为阻塞执行,用户A将库存改为9,用户BC同样将库存改为9,最后库存剩余还是9,这显然与预期结果不服,正确的剩余库存应该为7

扣库存应该采用 Update goods set stock = stock -1 where id = ? 这种语句,更新时读取的是当前记录的stock的值.最后修正结果代码如下:

//最后修正
DB::transaction(function () use ($request, $user_model) {
 $goods_model = Goods::lockForUpdate()->find($request->id);
 if ($goods_model->stock <= 0 || $goods_model->stock < $request->number) {
          throw new \Exception('库存不足');
         }
  //扣库存 正确的操作 或者使用原生update
  $goods_model->decrement('stock');
 ...
});

decrement 方法生成的语句为update goods set stock = stock -1 where id = ?

总结

  • 外包项目写多了忽略了程序在高并发场景下可能发生的情况.高并发场景要考虑进程线程之间数据一致性和以及程序和数据库(包含nosql) 的数据一致性问题
  • 遇到问题要反复仔细验证,不要轻易下结论

疑问

使用错误的的写法用JMeter模拟并发兑换 20threads/1s 200threads/1s 都没有出现库存超扣现象,由于生产环境不允许压测,所以在本机测试 .猜测可能本机的配置高于服务器,处理能力高于服务器所有没有复现,以后验证成功回头再补充

本机(8核16G) 服务器(4核8G)而且跑了很多项目

解决方案引申

乐观锁

//乐观锁
    public function caseOne(Request $request)
    {
        $this->validate($request, [
            'number' => 'bail|required|integer|min:1',
            'goods_id' => [
                'bail', 'required', 'integer',
                Rule::exists('goods', 'id')->where(function ($query) use ($request) {
                    $query->where('stock', '>=', $request->number);
                }),
            ]
        ]);
        DB::transaction(function () use ($request) {
            $goods = Goods::find($request->goods_id);
            $result = DB::update('update goods set stock = stock - ? where id = ? and stock >= ?', [$request->number, $request->goods_id, $request->number]);
            if (!$result) {
                return response()->json(['message' => '失败']);
            }
            Orders::create([
                'goods_id' => $goods->id,
                'goods_name' => $goods->name,
                'number' => $request->number,
            ]);
        });

        return response()->json();
    }

悲观锁

//悲观锁
    public function caseTwo(Request $request)
    {
        $this->validate($request, [
            'number' => 'bail|required|integer|min:1',
            'goods_id' => [
                'bail', 'required', 'integer',
                Rule::exists('goods', 'id')->where(function ($query) use ($request) {
                    $query->where('stock', '>=', $request->number);
                }),
            ]
        ]);
        DB::transaction(function () use ($request) {
            $goods = Goods::lockForUpdate()->find($request->goods_id);
            $result = DB::update('update goods set stock = stock - ? where id = ? and stock >= ?', [$request->number, $request->goods_id, $request->number]);
            if (!$result) {
                return response()->json(['message' => '失败']);
            }
            Orders::create([
                'goods_id' => $goods->id,
                'goods_name' => $goods->name,
                'number' => $request->number,
            ]);
        });

        return response()->json();
    }

不用事务

//不用事务
    public function caseThree(Request $request)
    {
        $this->validate($request, [
            'number' => 'bail|required|integer|min:1',
            'goods_id' => [
                'bail', 'required', 'integer',
                Rule::exists('goods', 'id')->where(function ($query) use ($request) {
                    $query->where('stock', '>=', $request->number);
                }),
            ]
        ]);
        $goods = Goods::find($request->goods_id);
        $result = DB::update('update goods set stock = stock - ? where id = ? and stock >= ?', [$request->number, $request->goods_id, $request->number]);
        if (!$result) {
            return response()->json(['message' => '失败']);
        }
        Orders::create([
            'goods_id' => $goods->id,
            'goods_name' => $goods->name,
            'number' => $request->number,
        ]);
    }

redis库存

//redis库存
    public function caseFour(Request $request)
    {
        $this->validate($request, [
            'number' => 'bail|required|integer|min:1',
            'goods_id' => [
                'bail', 'required', 'integer',
                function ($attributes, $value, $fail) use ($request) {
                    $stock = Redis::get('goods_id_' . $value);
                    if (is_null($stock)) {
                        return $fail('商品不存在');
                    }
                    if ($stock < $request->number) {
                        return $fail('库存不足');
                    }
                }
            ]
        ]);
        $goods = Goods::find($request->goods_id);
        $result = DB::update('update goods set stock = stock - ? where id = ? and stock >= ?', [$request->number, $request->goods_id, $request->number]);
        if (!$result) {
            return response()->json(['message' => '失败']);
        }
        Orders::create([
            'goods_id' => $goods->id,
            'goods_name' => $goods->name,
            'number' => $request->number,
        ]);
        Redis::decr('goods_id_' . $request->goods_id);

        return response()->json();
    }

队列

public function caseFive(Request $request)
    {
        $this->validate($request, [
            'number' => 'bail|required|integer|min:1',
            'goods_id' => [
                'bail', 'required', 'integer',
                function ($attributes, $value, $fail) use ($request) {
                    $stock = Redis::get('goods_id_' . $value);
                    if (is_null($stock)) {
                        return $fail('商品不存在');
                    }
                    if ($stock < $request->number) {
                        return $fail('库存不足');
                    }
                }
            ]
        ]);
        $goods = Goods::find($request->goods_id);
        SecKill::dispatch($goods, $request->all());

        return response()->json();
    }

https://github.com/yangliuan/shop-demo 有待完善

微博删除脚本

// ==UserScript==

// @name Weibored.js

// @namespace http://vito.sdf.org

// @version 0.2.0

// @description 删除所有微博

// @author Vito Van

// @match http://weibo.com/p/*

// @grant none

// ==/UserScript==

'use strict';

var s = document.createElement('script');

s.setAttribute(

'src',

'https://lib.sinaapp.com/js/jquery/2.0.3/jquery-2.0.3.min.js'

);

s.onload = function() {

setInterval(function() {

if (!$('a[action-type="feed_list_delete"]')) {

$('a.next').click();

} else {

$('a[action-type="feed_list_delete"]')[0].click();

$('a[action-type="ok"]')[0].click();

}

// scroll bottom let auto load

$('html, body').animate({ scrollTop: $(document).height() }, 'slow');

}, 800);

};

document.head.appendChild(s);

打开微博个人首页.F12打开控制台,输入代码回车

WEB应用常见漏洞

Sql注入

原理:利用网页程序没有对传参进行过滤,执行不允许的sql语句获取重要数据 百度百科 维基百科

防御

后端程序对请求参数进行严格过滤,使用框架的查询构造器和ORM操作Sql,禁止直接用代码拼接sql语句

SQL注入详解

XSS(Cross site scripting 跨站脚本攻击)

原理 前端运行了用户输入的恶意js脚本

MDN说明

防御

php函数htmlspecialchars转义html标签

过滤敏感js标签 ezyang/htmlpurifier

现代前端框架会自动处理xss问题

PHP防御 XSS 安全漏洞 – HTMLPurifier

如何防止XSS攻击?美团点评

PHP如何防止XSS攻击

CSRF(Cross-site request forgery 跨站请求伪造)

原理 MDN文档说明

防御

如何防止CSRF攻击?-美团点评

SSRF(Server-Side Request Forgery 服务器端请求伪造)

原理

防御

XXE(XML外部实体注入)

原理

防御

参考

Webshell查杀工具

1.百度出品 WEBDIR+ https://scanner.baidu.com/#/pages/intro

web版在线查杀使用方便,上传时推荐使用压缩率比较高的格式,如xz文件

2.WebShellkiller http://edr.sangfor.com.cn/backdoor_detection.html

推荐使用第二种方式检出数量优先,然后人工排除,第一种有方式有很多扫描不出来

3.河马查杀 http://www.shellpub.com/

4 .D盾查杀 http://www.d99net.net

总结

1.开源框架或cms产品及时更新安全补丁,查不出结果时,对比目录结构文件,使用tree命令,然后人工审核,混入一堆乱码(免杀处理)的都是木马.

2.扫描站点的运行木目录,nginx 或apache配置的root目录 .laravel 或tp5框架 运行目录为public ,木马只能放在此目录运行,主要扫描这个目录就可以

3.查找站点下最近修改的php文件.

http://man.linuxde.net/find

  • find ./ -name “*.php” -mtime 0 查找当前目录下24小时内更改的php文件
  • find ./ -name “*.php” -mmin -60 查找当前目录下60分钟内更改的PHP文件

参考

10款常见的Webshell检测工具

网络安全应急响应实战

跨域资源共享 CORS 详解

转自 阮一峰老师博客 转载只为备忘,侵权请联系yangliuancn@foxmail.com  阅读原文

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。

它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

本文详细介绍CORS的内部机制。

一、简介

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

二、两种请求

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要同时满足以下两大条件,就属于简单请求。

(1) 请求方法是以下三种方法之一:

  • HEAD
  • GET
  • POST

(2)HTTP的头信息不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

凡是不同时满足上面两个条件,就属于非简单请求。

浏览器对这两种请求的处理,是不一样的。

三、简单请求

3.1 基本流程

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。


GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequestonerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。


Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。

(1)Access-Control-Allow-Origin

该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

(2)Access-Control-Allow-Credentials

该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

(3)Access-Control-Expose-Headers

该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。

3.2 withCredentials 属性

上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。


Access-Control-Allow-Credentials: true

另一方面,开发者必须在AJAX请求中打开withCredentials属性。


var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。

但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials


xhr.withCredentials = false;

需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

四、非简单请求

4.1 预检请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

下面是一段浏览器的JavaScript脚本。


var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

上面代码中,HTTP请求的方法是PUT,并且发送一个自定义头信息X-Custom-Header

浏览器发现,这是一个非简单请求,就自动发出一个”预检”请求,要求服务器确认可以这样请求。下面是这个”预检”请求的HTTP头信息。


OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

“预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

除了Origin字段,”预检”请求的头信息包括两个特殊字段。

(1)Access-Control-Request-Method

该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT

(2)Access-Control-Request-Headers

该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header

4.2 预检请求的回应

服务器收到”预检”请求以后,检查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。


Access-Control-Allow-Origin: *

如果浏览器否定了”预检”请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。


XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

服务器回应的其他CORS相关字段如下。


Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000

(1)Access-Control-Allow-Methods

该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次”预检”请求。

(2)Access-Control-Allow-Headers

如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。

(3)Access-Control-Allow-Credentials

该字段与简单请求时的含义相同。

(4)Access-Control-Max-Age

该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

4.3 浏览器的正常请求和回应

一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

下面是”预检”请求之后,浏览器的正常CORS请求。


PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面头信息的Origin字段是浏览器自动添加的。

下面是服务器正常的回应。


Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8

上面头信息中,Access-Control-Allow-Origin字段是每次回应都必定包含的。

五、与JSONP的比较

CORS与JSONP的使用目的相同,但是比JSONP更强大。

JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

补充

最近做一个项目遇到坑点,由于使用的请求头太多,为了省事,跨域请求头直接设置了*

导致了在firefox浏览器和APP的WebView中跨域,Chrome正常

php设置请求头如下

header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: *');
header('Access-Control-Allow-Methods: *');

报错误如下,使用options预请求时,来自Access-Control-Allow-Headers 的令牌 ‘authorization’,无效

修正如下,需要指定具体请求头和请求方法

header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: Authorization, Content-Type, Access-Control-Allow-Headers, X-Requested-With');
header('Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,OPTIONS');

曳光弹

曳光弹

曳光弹和常规子弹交错装在弹药带上,发射时会照亮弹道路线,如果曳光弹击中目标,那么常规子弹也会击中目标,比提前费力计算更可取,可以及时获得反馈。

类比软件开发,曳光弹适用于新项目。当你构建从未构建过的东西时,与枪手一样,你也没法在黑暗中击中目标。因为你的用户从未见过这样的系统,他们的需求可能会含糊不清。因为你在使用不熟悉的算法、技术、语言或库,你面对这当量未知的事物。同时因为完成项目需要时间,在很大程度上能够确知,你的工作环境将在你完成之前发生变化。

经典的作法是把系统定死,制作大量的文档,注意列出每项需求、确定所有未知因素。限定环境,预先进行大量计算。这种方式非常低效。因为永远有你考虑不到的情况。

注重实效的作法是使用曳光弹,我们要找到某种东西,让我们快速直观和可重复的从需求出发,满足最终系统的某个方面要求。曳光代码并非用过就扔的代码,你编写它是为了保留它。它只是功能不全,可以逐步完善达到最终目标。

曳光开发与项目永不会结束的理念是一致的:总有改动需要完成,总有功能需要增加。这是一个渐进的过程。曳光弹算一种敏捷开发方法。

曳光开发不一定命中目标达到想要的效果。

参考

《程序员的修炼之道从小工到专家》