Coding & Life

求知若饥,虚心若愚

0%

跨站请求伪造(CSRF)

原理

危害是攻击者可以盗用你的身份,以你的名义发送恶意请求。比如可以盗取你的账号,以你的身份发送邮件,购买商品等

例子

在某个论坛管理页面,管理员可以在list.php页面执行删除帖子操作,根据URL判断删除帖子的id,像这样的一个URL

1
http://localhost/list.php?action=delete&id=12

当恶意用户想管理员发送包含CSFR的邮件,骗取管理员访问 http://test.com/csrf.php 在这个恶意网页中只要包含这样的html语句就可以利用让管理员在不知
情的情况下删除帖子了

1
<img alt="" src="http://localhost/list.php?action=delete&id=12"/>

使用post修改信息就安全了么?

1
2
3
4
5
<?php
$action=$_POST['action'];
$id=$_POST['id'];
delete($action,$id);
?>

同样可以攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
  <body>
    <iframe display="none">
      <form method="post" action="http://localhost/list.php">
        <input type="hidden" name="action" value="delete">
        <input type="hidden" name="id" value="12">
<input id="csfr" type="submit"/>
      </form>
    </iframe>

<script type="text/javascript">
     document.getElementById('csfr').submit();
    </script>
  </body>
</html>

如何防范

  1. 使用post,不使用get修改信息
  2. 验证码,所有表单的提交需要验证码,但是貌似用起来很麻烦,所以一些关键的操作可以
  3. 在表单中预先植入一些加密信息,验证请求是此表单发送

跨站脚本攻击(XSS)

原理

跨站脚本攻击(Cross Site Script为了区别于CSS简称为XSS)指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的htm
l代码会被执行,从而达到恶意用户的特殊目的

例子

我们有个页面用于允许用户发表留言,然后在页面底部显示留言列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html>
<html>
<head>
<?php include('/components/headerinclude.php');?></head>
<style type="text/css">
.comment-title{
font-size:14px;
margin: 6px 0px 2px 4px;
}

.comment-body{
font-size: 14px;
color:#ccc;
font-style: italic;
border-bottom: dashed 1px #ccc;
margin: 4px;
}
</style>
<script type="text/javascript" src="/js/cookies.js"></script>
<body>
<form method="post" action="list.php">
<div style="margin:20px;">
<div style="font-size:16px;font-weight:bold;">Your Comment</div>
<div style="padding:6px;">
Nick Name:
<br/>
<input name="name" type="text" style="width:300px;"/>
</div>
<div style="padding:6px;">
Comment:
<br/>
<textarea name="comment" style="height:100px; width:300px;"></textarea>
</div>
<div style="padding-left:230px;">
<input type="submit" value="POST" style="padding:4px 0px; width:80px;"/>
</div>
<div style="border-bottom:solid 1px #fff;margin-top:10px;">
<div style="font-size:16px;font-weight:bold;">Comments</div>
</div>
<?php
require('/components/comments.php');
if(!empty($_POST['name'])){
addElement($_POST['name'],$_POST['comment']);
}
renderComments();
?>
</div>
</form>
</body>
</html>

addElement()方法用于添加新的留言,而renderComments()方法用于展留言列表,网页看起来是这样的

因为我们完全信任了用户输入,但有些别有用心的用户会像这样的输入

这样无论是谁访问这个页面的时候控制台都会输出“Hey you are a fool fish!”,如果这只是个恶意的小玩笑,有些人做的事情就不可爱了,有些用户会利用
这个漏洞窃取用户信息、诱骗人打开恶意网站或者下载恶意程序等

如何防范

上面演示的是一个非常简单的XSS攻击,还有很多隐蔽的方式,但是其核心都是利用了脚本注入,因此我们解决办法其实很简单,不信赖用户输入,对特殊字符如”<”,”>”转义,就可以从根本上防止这一问题,当然很多解决方案都对XSS做了特定限制,如上面这中做法在ASP.NET中不幸不同,微软validateRequest对表单提交自动做了XSS验证。但防不胜防,总有些聪明的恶意用户会到我们的网站搞破坏,对自己站点不放心可以看看这个XSS跨站测试代码大全试试站点是否安全。

1
nohup java -jar yatai_pro-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod >/dev/null &

/dev/null 表示将标准输出信息重定向到”黑洞”

轮询(默认策略)

每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除

指定权重

指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。

1
2
3
4
upstream servername { 
server 192.168.0.1 weight=1;
server 192.168.0.2 weight=12;
}

IP绑定 ip_hash

每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。

1
2
3
4
5
upstream backserver { 
ip_hash;
server 192.168.0.14:88;
server 192.168.0.15:80;
}

fair(第三方)

按后端服务器的响应时间来分配请求,响应时间短的优先分配。

1
2
3
4
5
upstream backserver { 
server server1;
server server2;
fair;
}

这种策略具有很强的自适应性,但是实际的网络环境往往不是那么简单,因此须慎用。

url_hash(第三方)

按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。

1
2
3
4
5
6
upstream backserver { 
server squid1:3128;
server squid2:3128;
hash $request_uri;
hash_method crc32;
}

问题:CentOS安装nginx后,通过外网不能访问

怀疑:nginx使用80端口,被防火墙拦截

解决办法

编辑防火墙配置文件

1
vim /etc/firewalld/zones/public.xml

增加80端口的开放

1
<port protocol="tcp" port="80"/>

重启防火墙

1
systemctl restart firewalld.service

删除老版本

1
sudo apt-get remove firefox

下载最新软件包

1
http://www.firefox.com.cn/download/

解压到本地

移动文件到/opt文件夹下

1
sudo mv firefox /opt

创建桌面图标

在/usr/share/applications下创建firefox.desktop文件

1
sudo touch firefox.desktop

编辑文件

1
2
3
4
5
6
7
8
9
[Desktop Entry]
Name=Firefox
Name[zh_CN]=火狐浏览器
Comment=this is firefox
Exec=/opt/firefox/firefox
Icon=/opt/firefox/icons/fox.png
Terminal=false
Type=Application
Categories=Application;Network;

pic

购买vultr服务

注册vultr,购买服务(建议地址选Tokyo,操作系统选择CentOS,配置选择最小配置),然后部署服务器

安装shadowsocks

本文安装是是使用CentOS7系统

连接服务器

依次执行以下命令

1
2
3
wget –no-check-certificate https://raw.githubusercontent.com/teddysun/shadowsocks_install/master/shadowsocks.sh
chmod +x shadowsocks.sh
./shadowsocks.sh 2>&1 | tee shadowsocks.log

等几秒钟,根据提示输入shadowsocks密码及端口号

安装ss

成功后出现以下页面,妥善保存信息

安装成功后

配置维护Shadowsocks

配置多用户多端口

修改/etc/shadowsock.json文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"server":"my_server_ip",
"local_address": "127.0.0.1",
"local_port":1080,
"port_password": {
"8381": "foobar1",
"8382": "foobar2",
"8383": "foobar3",
"8384": "foobar4"
},
"timeout":300,
"method":"aes-256-cfb",
"fast_open": false
}

其中,port_password就是将之前的服务器端口和对应的密码结合起来。然后通过使用以下命令,就可以启动多端口多用户了。

1
2
3
4
启动:systemctl start shadowsocks.service
停止:systemctl stop shadowsocks.service
重启:systemctl restart shadowsocks.service
状态:systemctl status shadowsocks.service

centos默认的防火墙机制,会阻隔掉我们的多端口配置。所以,解决方法就是,将这个端口打开tcp和udp通信。这里需要说明的事,在centos版本的更新迭代过程中,centos7和centos6之间的差异性较大。在centos7,采用的是最新的防火墙filewall而不是传统的iptables。
具体操作为,先进入firewalled的配置端口目录,路径为etc/firewalled/zones打开public.xml文件进行端口的编辑.例如

1
2
<port protocol="tcp" port="449"/>   
<portprotocol="udp" port="443"/>

即添加了tcp和udp的权限.然后systemctl restart firewalld.service 重启防火墙,端口就添加到防火墙的白名单中啦。

下载(Shadowsocks客户端)

mac和windows

访问

1
https://github.com/shadowsocks

client
Shadowsocks-windows为windows客户端
ShadowsocksX-NG为mac客户端

iphone

商店搜索super wingy
super_wingy

KCPTun加速

搭建好了Shadowsocks相当于搭建成功了梯子,但是梯子太长,即时梯子带宽足够宽,线路质量也是不忍直视,此时就需要KCPTun了。

KCPTun是一个使用可信UDP来加速TCP传输速度的网络软件。

KCP 是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%,且最大延迟降低三倍的传输效果。

KCPTun服务安装

1
2
3
4
5
6
// 下载脚本
wget https://raw.githubusercontent.com/kuoruan/kcptun_installer/master/kcptun.sh
// 赋予权限
chmod +x ./kcptun.sh
// 执行脚本
./kcptun.sh

vultr购买推荐地址

1
https://www.vultr.com/?ref=7259520

最近发现购买的vps总是有很多次失败尝试登录的记录,感觉很不安全,遂想到修改ssh默认端口来降低风险

修改配置文件

1
vim /etc/ssh/sshd_config

在**#Port 22下增加端口Port 10001**,去掉22端口的注释(以防修改端口失败,默认端口也不能登录)

SSH默认监听端口就是22,上面我保留了22端口,以防修改端口失败,默认端口也不能登录

增加10001端口,大家修改端口时候最好挑10000~65535之间的端口号,10000以下容易被系统或一些特殊软件占用,或是以后新应用准备占用该端口的时候,却被你先占用了,导致软件无法运行。

修改SELinux配置

查看是否开启了SELinux

1
sestatus -v

如果没有开启,则跳过这一步

查看SELinux开放给ssh使用的端口

1
semanage port -l|grep ssh

ssh_port_t tcp 22

SELinux开放10001端口给ssh

1
semanage port -a -t ssh_port_t -p tcp 10001

完成后再次查看

1
semanage port -l|grep ssh

ssh_port_t tcp 22,10001

防火墙放开10001端口

修改防火墙配置文件

1
vim /etc/firewalld/zones/public.xml

增加100001的端口放开

1
<port protocol="tcp" port="10001"/>

重启

1
2
3
systemctl restart sshd   重启ssh
systemctl restart firewalld.service 重启防火墙
shutdown -r now 重启服务器

重新登录

1
ssh root@localhost -p 10001

如果登录成功注释掉上边的22端口配置

修改了默认端口之后尝试登录的攻击显著降低

pic

问题描述

最近搞微信公众平台接口时,我们获得access_token的处理方式是:先在缓存中查找,如果找到,返回;否则,调用微信接口获得,放入缓存(有时效),然后返回;

此处的逻辑并不严谨

如果有两个线程 同时 获得access_token,而且此时恰好access_token在缓存中因为过期而删除了,那么两个线程都会调用微信接口请求access_token,这时就会出现第二次请求的结果把第一次的结果覆盖掉的情况。

最初实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public JSONObject getAccessToken(String appid, String appsecret) {
String reqestUrl = ACCESS_TOKEN_URL.replace("APPID", appid).replace("APPSECRET", appsecret);

RedisUtils<String> redisUtils = new RedisUtils<>(redisTemplate);

String accessToken = redisUtils.get(ACCESS_TOKEN_CACHE_KEY);

if (StrUtil.isNotEmpty(accessToken)) {
return JSONObject.parseObject(accessToken);
}

JSONObject resultJSON = JSONObject.parseObject(HttpUtil.get(reqestUrl));

if (StrUtil.isNotEmpty(resultJSON.getString("errcode"))) {
log.info(resultJSON.getString("errmsg"));
return null;
}

redisUtils.set(ACCESS_TOKEN_CACHE_KEY, resultJSON.toJSONString(), 7000L);

return resultJSON;
}

解决方法

把请求微信接口的方法加入同步代码块中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public JSONObject getAccessToken(String appid, String appsecret) {
String reqestUrl = ACCESS_TOKEN_URL.replace("APPID", appid).replace("APPSECRET", appsecret);

RedisUtils<String> redisUtils = new RedisUtils<>(redisTemplate);

String accessToken = redisUtils.get(ACCESS_TOKEN_CACHE_KEY);

if (StrUtil.isNotEmpty(accessToken)) {
return JSONObject.parseObject(accessToken);
}

// 防止高并发情况下,access_token覆盖的问题
synchronized (appid) {
// 再次验证缓存中的数据
String accessToken2 = redisUtils.get(ACCESS_TOKEN_CACHE_KEY);
if (StrUtil.isNotEmpty(accessToken2)) {
log.info("multi thread hits!");
return JSONObject.parseObject(accessToken2);
}

JSONObject resultJSON = JSONObject.parseObject(HttpUtil.get(reqestUrl));

if (StrUtil.isNotEmpty(resultJSON.getString("errcode"))) {
log.info(resultJSON.getString("errmsg"));
return null;
}

redisUtils.set(ACCESS_TOKEN_CACHE_KEY, resultJSON.toJSONString(), 7000L);

return resultJSON;
}
}