修改默认的 Pi 账号

树莓派默认的登录帐号是Pi,为了安全和使用习惯的统一,修改这个默认的 Pi 账号步骤如下:

首先,需要启用 root 账号。

sudo passwd ** sudo passwd –unlock root

**为root的密码,按照个人的想法来设置,这个时候要你输入两遍。设置 root 密码后,退出当前的 pi 用户。以 root 身份登录。

先修改 user

usermod -l yourname pi

再修改 group

groupmod -n yourname pi

再把 home 目录改掉

mv /home/pi /home/yourname

设置一下 home 目录

usermod -d /home/yourname yourname

好了,现在可以用新账号登录。资料完全是原来的 pi 的。

最后,为了安全起见,还是禁用掉 root。

sudo passwd -l root

通过SSH配置Wi-Fi连接

脚本如下,修改其中的SSID、ENCRYPTION以及PASSWORD。

#! /bin/sh # SSID (aka. network name). SSID=’Tenda’ # Network encryption method. # * ‘WPA’ for WPA-PSK/WPA2-PSK (note: most Wi-Fi networks use WPA); # * ‘WEP’ for WEP; # * ‘Open’ for open network (aka. no password). ENCRYPTION=’WPA’ # Network password. (WPA-PSK/WPA2-PSK password, or WEP key) PASSWORD=’pass’ if [ $(id -u) -ne 0 ]; then printf “This script must be run as root.

” exit 1 fi NETID=$(wpa_cli add_network | tail -n 1) wpa_cli set_network $NETID ssid \”$SSID\” case $ENCRYPTION in ‘WPA’) wpa_cli set_network $NETID key_mgmt WPA-PSK wpa_cli set_network $NETID psk \”$PASSWORD\” ;; ‘WEP’) wpa_cli set_network $NETID wep_key0 $PASSWORD wpa_cli set_network $NETID wep_key1 $PASSWORD wpa_cli set_network $NETID wep_key2 $PASSWORD wpa_cli set_network $NETID wep_key3 $PASSWORD ;; *) ;; esac wpa_cli enable_network $NETID wpa_cli save_config

等待几秒钟之后,无线网卡上的指示灯亮起,树莓派即连接成功。

可能用到的命令:

lsusb – 查看USB设备,检查无线网卡。

lsmod – 查看系统已加载的模块。rt2x00 是 Ralink 芯片组的统一驱动。

iwconfig – 查看网卡信息。

ifconfig – 查看连接信息。ifconfig wlan0 up 启动网卡 wlan0。

供电不足可能会导致USB无线网卡易掉线等问题,需要保持电力供应。

如果在wpa_gui中找不到网卡适配器,在 /etc/wpa_supplicant/wpa_supplicant.conf 中加入这两行配置就行了:

# needed for wpa_gui to work ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev # needed to allow wpa_gui to alter the configuration update_config=1

via

基于 Samba 实现 NAS 系统

摆弄了几天Raspberry Pi,在搞定了无线网络、FTP服务之后,打算更进一步,通过Samba实现NAS系统与PC共享文件。

需要安装的软件:

sudo apt-get install samba samba-common-bin

sudo apt-get install netatalk (可选,用于支持AFP)

sudo apt-get install avahi-daemon(可选,用于支持网内的计算机自动发现)

接下来就是配置了:

[Samba 配置] /etc/samba/smb.conf 文件尾部增加

[public] comment = Public Storage path = /home/pi read only = no#任何人都具有了访问修改的权限 #因为是公共文件夹,所以给了所有用户全部权限,可以自定义 create mask = 0777#新创建文件的默认属性 directory mask = 0777#新创建文件夹的默认属性 guest ok = yes#默认的访问用户名为guest browseable = yes

有关 Samba 的详细配置可以参考资料1、资料2。然后就可以 smbd restart 了。这时候已经可以通过网上邻居看到共享文件目录,只是进不去。提示需要输入口令,尝试用本地帐户也无法进入。查了资料原来因为 Samba 使用了自己一套用户帐号资料库。要登录的话还需要向该库添加帐号信息,方法有两种:

1.直接用 mksmbpasswd.sh 将系统用户转换成 Samba 用户:

cat /etc/passwd | mksmbpasswd.sh >/etc/samba/smbpasswd

2.用 smbpasswd 命令直接设置,需要首先要添加系统用户然后用 smbpasswd -a 用户名 添加 Samba 用户;smbpasswd -e 用户名 激活用户。

[Netatalk 配置] /etc/netatalk/AppleVolumes.default 方法参考资料3。

最后就是挂载USB移动硬盘了:

mount /dev/sda1 /home/shares/public/

有时候卸载USB移动硬盘的时候会提示设备忙(Device is busy),只需要加上 –l 参数就行了:

umount -l /home/shares/public/

via

修改SWAP空间大小

cd /var sudo swapoff /var/swapfile sudo dd if=/dev/zero of=swapfile bs=1M count=1024 sudo mkswap /var/swapfile sudo swapon /var/swapfile sudo nano /etc/fstab

其中count=1024等于你想让SWAP设置为多少mb,我设置为1G 然后在文件尾部添加一行,就大功告成了。

/var/swapfile none swap sw 0 0

via

树莓派搭建LNMP环境

2021.3.3 更新:新版本的 NGINX 和 PHP 安装方法请参考这里

Raspberry Pi的固件有很多,我安装的是官方的Raspbian。配置ip。然后ssh上去后开始安装。

推荐root权限执行

sudo apt-get update sudo apt-get install nginx php5-fpm php5-cli php5-curl php5-gd php5-mcrypt php5-mysql php5-cgi mysql-server

首次安装mysql的时候会提示输入密码,密码不要忘记就行了。

接下来我们来配置Nginx,首先打开配置文件,/etc/nginx/nginx.conf ,按照下面的配置进行修改。

worker_processes 1; worker_connections 256; gzip on; gzip_disable “msie6”; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_buffers 16 8k; gzip_http_version 1.1; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

接下来打开/etc/nginx/sites-available/default也是按照下面的配置进行修改。

server { listen 80;#Web服务端口号,大陆用户可能需要修改为81或8080等 server_name raspiweb.dyndns.org; root /media/usb/www/; access_log /var/log/nginx/localhost.access.log; #error_page 404 /404.html; if (!-e $request_filename) { rewrite ^(.*)$ /index.php$1 last; } location / { index index.html index.htm index.php default.html default.htm default.php; } location ~* ^.+.(jpg|jpeg|gif|css|png|js|ico|xml)$ { access_log off; expires 1d; } location ~ .*\.php(\/.*)*$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/var/run/php5-fpm.sock; fastcgi_index index.php; include fastcgi_params; } }

下面是对mysql的调优,打开配置文件/etc/mysql/my.cnf修改以下几处。

[mysqld] key_buffer = 16k max_allowed_packet = 1M thread_stack = 64K thread_cache_size = 4 query_cache_limit = 1M default-storage-engine = MYISAM

最后我们来配置php.ini,php-fpm,打开配置文件/etc/php5/fpm/php.ini和/etc/php5/fpm/php-fpm.conf修改以下几处。

memory_limit=16M process.max=4

重启nginx。

sudo /usr/sbin/nginx -s reload

到这里我们的lnmp环境配置已经大功告成!接下来是安装phpmyadmin到指定目录~这里只讲如何配置lnmp。

via

使用静态 IP 地址

MCC 172是一个两通道DAQ HAT,用于通过IEPE传感器(如加速度计和麦克风)进行声音和振动测量。它的每个通道具有24位A/D,最大采样率为51.2 kS/s/Ch。

[转]古诗中的”斜“究竟怎么读

《小学语文教师》2000年第5期上刊有胡孟铭老师的文章《“斜”在〈山行〉中还是读xia好》,主张将“斜”字还其旧读、读古音xia,理由是:“既然课题为古诗,就应读古韵,取古义,才能字义准确,和谐动听”。本刊2000年第10期上又刊有史南先生的文章《“斜”不可读xia》,予以反对,理由是:1985年国家公布的《普通话异读词审音表》“不管那些当时不存在异读的词;如‘斜’字在普通话字(词)典上早已取消了古读音,因此在《审音表》中没有收列它,表明它除了读xie之外,普通话中不存在别的读音”“小学语文教学大纲要求小学生能用普通话朗读课文……再坚持读古音,就违背了国家有关语言文字的政策法规”。

问题至此似乎已有了答案。但笔者的看法却与以上两位老师皆有所不同。语言文字的使用,的确如史老师所说,应当以国家的政策法规为依据。而最高的依据则是2000年10月31日九届人大常委会通过并颁布的《中华人民共和国国家通用语言文字法》(以下简称《语言文字法》)。两位老师讨论时,本法尚未公布。但本法自2001年1月1日实施以后,“斜”的读音问题理当有新的答案。

“斜”字确实存在着旧读音xia,后来演变出xie音,但意义没变(胡老师所谓“‘斜’的读音发生了变化,词义的范围也大大缩小了。倘若将‘斜’还其旧韵xia,(则有)古义‘倾侧或曲折向前延伸’”是无根据的,《康熙字典》《辞源》等古今字书辞书足以为训)。但在古诗教学中究竟应读古音xia,还是今音xie?则应视情形而论。
《语言文字法》规定:“本法所称的国家通用语言文字是普通话和规范汉字”,我们在教学中就应当以国家通用语言文字为准,这是毫无疑义的。所以,教材在古诗《山行》中将“斜”注音为xie是正确的,不但如此,“斜”在本篇课文中还是个生字,教师在教学“斜”这个单字时,也应当训为xie。胡老师对这一点提出异议,笔者不能苟同。但在读本诗的时候也读为xie,又确实拗口。本诗的“斜”字出现在首句,似乎还可将就一二,很多唐诗中“斜”字都出现在偶句末尾。我们知道,古诗的平仄、对仗、押韵,以押韵为第一,当时这些诗都是严格押韵的。若诵读这些诗时也读该字为xie,古诗固有的韵律美被破坏,更让人难于接受。如:
刘禹锡《乌衣巷》
朱雀桥边野草花,乌衣巷口夕阳斜。旧时王谢堂前燕,飞入寻常百姓家。
刘方平《夜月》
更深月色半人家,北斗阑干南斗斜。今夜偏知春气暖,虫声新透绿窗纱。
张泌《寄人》
别梦依依到谢家,小廊回合曲阑斜。多情只有春庭月,犹为离人照落花。
韩君平《寒食》
春城无处不飞花,寒食东风御柳斜。日暮汉宫传蜡烛,轻烟散入五侯家。
元稹《菊花》
秋丛绕舍似陶家,遍绕篱边日渐斜。不是花中偏爱菊,此花开尽更无花。
皎然《寻陆鸿渐不遇》
移家虽带郭,野径入桑麻。近种篱边菊,秋来未着花。扣门无犬吠,,欲去问西家。报道山中去,归来每日斜。
张若虚《春江花月夜》(节录)
昨夜闲潭梦落花,可怜春半不还家。江水流春去欲尽,江潭落月复西斜。
张祜《吴兴新堤》(节录)
春堤一望思无涯,树势还同水势斜。深映菰蒲三十里,晴分功利几千家。

史老师在文章最后说:“但为了让学生知道原来是押韵的,在教学时不妨告诉学生‘斜’在古代的韵母就读ia,直到今天,吴方言中还保留它的古读音,读zia”。笔者认为这样还不够,在诵读这些古诗时就应当读‘斜’为xia。因为教学语言文字与欣赏文学艺术是两回事。我们教学古诗时,这两种活动是同时存在的,但也是完全能够而且应该把二者区分开来的。有的老师就是这样教的:领着学生读“远上寒山石径斜xia……”,然后跟学生讲:“‘斜’字课本上注音为xie,就是‘歪斜’的意思,平时就应当读xie,为什么在诗里读xia呢?这是因为这个字在以前的韵母就是ia,这首诗当时是很押韵的。我们读诗时,为了体会诗的韵律美,也为了更上口,就暂时读xia”。可以确信,只要老师教得明白,学生是不会在其他场合、在说话和写作应用中也读为xia的。这样,不但不会影响到语言文字规范化纯洁化的问题,相反学生还会多学到一点知识,知道“斜”字在古音韵母读ia,知道古人作诗是很重押韵的,更能体会到古诗的韵律美,更容易体会到前人的诗歌艺术才华和成就。岂不更好?重要的是,这样做也有法律的依据。《语言文字法》明确规定:“有下列情形的,可以使用方言:……三戏曲影视等艺术形式中需要使用的;四出版、教学、研究中确需使用的”。这里所谓的“方言”,当然也包括像“斜xia”这样的以方言形式存在的古音韵母。

当然,这种“暂时地读为古音”也是有条件的:必须是“斜”字作韵脚,在韵律的“胁迫”下自然而然地读为xia;如果“斜”虽在古诗中,但不作韵脚,则须还其正读xie音。

对于这种处理,我们还可从对古字形的处理当中找到类似的旁证。比如胡、史二位老师讨论的“鹿柴”。“鹿柴”中“柴”之所以读zhai,是因为“柴”是“寨”的通假字。但是在现代汉语用字标准中,“寨”是不能再写作“柴”了。课本之所以仍保留原来的字形“柴”,无非也是让学生多一点知识,知道“寨”在古代可通写作“柴”,为以后直接阅读古书提供方便而已。《语言文字法》也有相应的规定:“有下列情形的,可以保留或使用繁体字、异体字:……三书法、篆刻等艺术作品;……五出版、教学、研究中需要使用的”。
至于汉匈奴单于“伊稚斜”仍读cha,不读xie,则是因为《审音表》“对大量的人名、地名中的异读字未加审订,这些人名、地名中的异读字照样异读”。

在欣赏性的唐诗吟诵中,为求合于韵律、为求顺口而需要暂时恢复原来读音的字,还有很多。除“斜”字外,另一个比较常见的是“回”字。古诗中“回”作韵脚时,应读古音huai,而不读hui。如:
李白《望天门山》
天门中断楚江开,碧水东流至此回。两岸青山相对出,孤帆一片日边来。
杜甫《登高》(节录)
风急天高猿啸哀,渚清沙白鸟飞回。无边落木潇潇下,不尽长江滚滚来。
刘禹锡《戏赠看花诸君子》
紫陌红尘拂面来,无人不道看花回。玄都观里桃千树,尽是刘郎去后栽。
刘禹锡《石头城》
山围故国周遭在,潮打空城寂寞回。淮水东边旧时月,夜深还过女墙来。
杜牧《早雁》
金河秋半虏弦开,云外惊飞四散哀。仙掌月明孤影过,长门灯暗数声来。须知胡骑纷纷在,岂逐春风一一回。莫厌潇湘少人处,水多菰米岸莓苔。

“回”在唐诗韵脚中读huai,当无疑义。不过这又牵涉到另一个“衰”的读音。《长歌行》有“阳春布德泽,万物生光辉。长恐秋节至,焜黄华叶衰。百川东到海,何时复西归。”;贺知章《回乡偶书》有“少小离家老大回,乡音无改鬓毛衰。儿童相见不相识,笑问客从何处来。”--“衰”当读何音?历来说法不一:或有前首训为shuai的,或有后首训为cui的。笔者的意见则是:“衰”在前诗中训为cui,或有后诗中训为shuai。因为:“衰”字,作“形容萎缩、体量递减”时读cui,如“等衰”;作“气色衰老”时读shuai,如“衰草”。因此,“焜黄华叶衰”中读cui,是指鲜亮的花和叶子萎缩脱落、是形体之变;而“鬓毛衰”中读shuai,是两鬓斑白之意,是气色之变,不是形体之变,不是鬓毛萎缩脱落之意(因为人的鬓毛是头发中最不易脱落的)。诗中的韵脚“辉、归”“回、来”等字可作旁证。

防范 CSRF 跨站请求伪造

CSRF(Cross-site request forgery,中文为跨站请求伪造)是一种利用网站可信用户的权限去执行未授权的命令的一种恶意攻击。通过伪装可信用户的请求来利用信任该用户的网站,这种攻击方式虽然不是很流行,但是却难以防范,其危害也不比其他安全漏洞小。

本文将简要介绍CSRF产生的原因以及利用方式,然后对如何避免这种攻击方式提供一些可供参考的方案,希望广大程序猿们都能够对这种攻击方式有所了解,避免自己开发的应用被别人利用。

CSRF也称作one-click attack或者session riding,其简写有时候也会使用XSRF

什么是CSRF?

简单点说,CSRF攻击就是 攻击者利用受害者的身份,以受害者的名义发送恶意请求。与XSS(Cross-site scripting,跨站脚本攻击)不同的是,XSS的目的是获取用户的身份信息,攻击者窃取到的是用户的身份(session/cookie),而CSRF则是利用用户当前的身份去做一些未经过授权的操作。

CSRF攻击最早在2001年被发现,由于它的请求是从用户的IP地址发起的,因此在服务器上的web日志中可能无法检测到是否受到了CSRF攻击,正是由于它的这种隐蔽性,很长时间以来都没有被公开的报告出来,直到2007年才真正的被人们所重视。

CSRF有哪些危害

CSRF可以盗用受害者的身份,完成受害者在web浏览器有权限进行的任何操作,想想吧,能做的事情太多了。

  • 以你的名义发送诈骗邮件,消息
  • 用你的账号购买商品
  • 用你的名义完成虚拟货币转账
  • 泄露个人隐私

产生原理以及利用方式

要完成一个CSRF攻击,必须具备以下几个条件:

  • 受害者已经登录到了目标网站(你的网站)并且没有退出
  • 受害者有意或者无意的访问了攻击者发布的页面或者链接地址

(图片来自网络,出处不明,百度来的😂)

整个步骤大致是这个样子的:

  1. 用户小明在你的网站A上面登录了,A返回了一个session ID(使用cookie存储)
  2. 小明的浏览器保持着在A网站的登录状态,事实上几乎所有的网站都是这样做的,一般至少是用户关闭浏览器之前用户的会话是不会结束的
  3. 攻击者小强给小明发送了一个链接地址,小明打开了这个地址,查看了网页的内容
  4. 小明在打开这个地址的时候,这个页面已经自动的对网站A发送了一个请求,这时候因为A网站没有退出,因此只要请求的地址是A的就会携带A的cookie信息,也就是使用A与小明之间的会话
  5. 这时候A网站肯定是不知道这个请求其实是小强伪造的网页上发送的,而是误以为小明就是要这样操作,这样小强就可以随意的更改小明在A上的信息,以小明的身份在A网站上进行操作

利用方式

利用CSRF攻击,主要包含两种方式,一种是基于GET请求方式的利用,另一种是基于POST请求方式的利用。

GET请求利用

使用GET请求方式的利用是最简单的一种利用方式,其隐患的来源主要是由于在开发系统的时候没有按照HTTP动词的正确使用方式来使用造成的。对于GET请求来说,它所发起的请求应该是只读的,不允许对网站的任何内容进行修改

但是事实上并不是如此,很多网站在开发的时候,研发人员错误的认为GET/POST的使用区别仅仅是在于发送请求的数据是在Body中还是在请求地址中,以及请求内容的大小不同。对于一些危险的操作比如删除文章,用户授权等允许使用GET方式发送请求,在请求参数中加上文章或者用户的ID,这样就造成了只要请求地址被调用,数据就会产生修改。

现在假设攻击者(用户ID=121)想将自己的身份添加为网站的管理员,他在网站A上面发了一个帖子,里面包含一张图片,其地址为http://a.com/user/grant_super_user/121

<img src="http://a.com/user/grant_super_user/121"/>

设想管理员看到这个帖子的时候,这个图片肯定会自动加载显示的。于是在管理员不知情的情况下,一个赋予用户管理员权限的操作已经悄悄的以他的身份执行了。这时候攻击者121就获取到了网站的管理员权限。

POST请求利用

相对于GET方式的利用,POST方式的利用更加复杂一些,难度也大了一些。攻击者需要伪造一个能够自动提交的表单来发送POST请求。

//

只要想办法实现用户访问的时候自动提交表单就可以了。

如何防范

防范原理

防范CSRF攻击,其实本质就是要求网站能够识别出哪些请求是非正常用户主动发起的。这就要求我们在请求中嵌入一些额外的授权数据,让网站服务器能够区分出这些未授权的请求,比如说在请求参数中添加一个字段,这个字段的值从登录用户的Cookie或者页面中获取的(这个字段的值必须对每个用户来说是随机的,不能有规律可循)。攻击者伪造请求的时候是无法获取页面中与登录用户有关的一个随机值或者用户当前cookie中的内容的,因此就可以避免这种攻击。

防范技术

Synchronizer token pattern

令牌同步模式(Synchronizer token pattern,简称STP)是在用户请求的页面中的所有表单中嵌入一个token,在服务端验证这个token的技术。token可以是任意的内容,但是一定要保证无法被攻击者猜测到或者查询到。攻击者在请求中无法使用正确的token,因此可以判断出未授权的请求。

Cookie-to-Header Token

对于使用Js作为主要交互技术的网站,将CSRF的token写入到cookie中

Set-Cookie: CSRF-token=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql; expires=Thu, 23-Jul-2015 10:25:33 GMT; Max-Age=31449600; Path=/

然后使用javascript读取token的值,在发送http请求的时候将其作为请求的header

X-CSRF-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql

最后服务器验证请求头中的token是否合法。

验证码

使用验证码可以杜绝CSRF攻击,但是这种方式要求每个请求都输入一个验证码,显然没有哪个网站愿意使用这种粗暴的方式,用户体验太差,用户会疯掉的。

简单实现STP

首先在index.php中,创建一个表单,在表单中,我们将session中存储的token放入到隐藏域,这样,表单提交的时候token会随表单一起提交

<?php
$token = sha1(uniqid(rand(), true));
$_SESSION['token'] = $token;
?>
<form action="buy.php" method="post">
    <input type="hidden" name="token" value="<?=$token; ?>" />
    ... 表单内容
</form>

在服务端校验请求参数的buy.php中,对表单提交过来的token与session中存储的token进行比对,如果一致说明token是有效的

<code>
  <?php if ($_POST['token'] != $_SESSION['token']) {
    // TOKEN无效
    throw new \Exception('Token无效,请求为伪造请求');
}
// TOKEN有效,表单内容处理
</code?>
</code>

对于攻击者来说,在伪造请求的时候是无法获取到用户页面中的这个token值的,因此就可以识别出其创建的伪造请求。

解析Laravel框架中的VerifyCSRFToken中间件

在Laravel框架中,使用了VerifyCSRFToken这个中间件来防范CSRF攻击。

在页面的表单中使用{{ CSRF_field() }}来生成token,该函数会在表单中添加一个名为_token的隐藏域,该隐藏域的值为Laravel生成的token,Laravel使用随机生成的40个字符作为防范CSRF攻击的token。

$this->put('_token', Str::random(40));

如果请求是ajax异步请求,可以在meta标签中添加token

<meta name="CSRF-token" content="{{ CSRF_token() }}"/>

使用jquery作为前端的框架时候,可以通过以下配置将该值添加到所有的异步请求头中

$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="CSRF-token"]').attr('content')
    }
});

在启用session的时候,Laravel会生成一个名为_token的值存储到session中。而使用前面两种方式在页面中加入的token就是使用的这一个值。在用户请求到来时,VerifyCSRFToken中间件会对符合条件的请求进行CSRF检查

if (
  $this->isReading($request) ||
  $this->runningUnitTests() ||
  $this->shouldPassThrough($request) ||
  $this->tokensMatch($request)
) {
  return $this->addCookieToResponse($request, $next($request));
}

throw new TokenMismatchException;

if语句中有四个条件,只要任何一个条件结果为true则任何该请求是合法的,否则就会抛出TokenMismatchException异常,告诉用户请求不合法,存在CSRF攻击。

第一个条件$this->isReading($request)用来检查请求是否会对数据产生修改

protected function isReading($request)
{
    return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']);
}

这里判断了请求方式,如果是HEADGETOPTIONS这三种请求方式则直接放行。你可能会感到疑惑,为什么GET请求也要放行呢?这是因为Laravel认为这三个请求都是请求查询数据的,如果一个请求是使用GET方式,那无论请求多少次,无论请求参数如何,都不应该最数据做任何修改

第二个条件顾名思义是对单元测试进行放行,第三个是为开发者提供了一个可以对某些请求添加例外的功能,最后一个$this->tokensMatch($request)则是真正起作用的一个,它是Laravel防范CSRF攻击的关键

$sessionToken = $request->session()->token();
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');

if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
  $token = $this->encrypter->decrypt($header);
}

if (! is_string($sessionToken) || ! is_string($token)) {
  return false;
}

return hash_equals($sessionToken, $token);

Laravel会从请求中读取_token参数的的值,这个值就是在前面表单中添加的CSRF_field()函数生成的。如果请求是异步的,那么会读取X-CSRF-TOKEN请求头,从请求头中读取token的值。

最后使用hash_equals函数验证请求参数中提供的token值和session中存储的token值是否一致,如果一致则说明请求是合法的。

你可能注意到,这个检查过程中也会读取一个名为X-XSRF-TOKEN的请求头,这个值是为了提供对一些javascript框架的支持(比如Angular),它们会自动的对异步请求中添加该请求头,而该值是从Cookie中的XSRF-TOKEN中读取的,因此在每个请求结束的时候,Laravel会发送给客户端一个名为XSRF-TOKEN的Cookie值

$response->headers->setCookie(
    new Cookie(
        'XSRF-TOKEN', $request->session()->token(), time() + 60 * $config['lifetime'],
        $config['path'], $config['domain'], $config['secure'], false
    )
);

写在最后

本文只是对CSRF做了一个简单的介绍,主要是侧重于CSRF是什么以及如何应对CSRF攻击。有一个事实是我们无法回避的:没有绝对安全的系统,你有一千种防御对策,攻击者就有一千零一种攻击方式,但不管如何,我们都要尽最大的努力去将攻击者拦截在门外。如果希望深入了解如何发起一个CSRF攻击,可以参考一下这篇文章 从零开始学CSRF。

作为一名web方向的研发人员,无论你是从事业务逻辑开发还是做单纯的技术研究,了解一些安全方面的知识都是很有必要的,多关注一些安全方向的动态,了解常见的攻击方式以及应对策略,必将在你成长为一名大牛的路上为你“推波助澜”。

参考

防范 CSRF 跨站请求伪造,首发于文章 – 伯乐在线

想写无Bug的安全代码?看防御性编程的艺术

为什么开发者不编写安全的代码?我们在这并不是要再一次讨论「整洁代码」。我们要从纯粹的实用观点出发,讨论软件的安全性和保密性。是的,因为不安全的软件不仅无用,而且还可怕。我们来看看什么是不安全的软件。

  • 1996年6月4日,欧洲航天局的 Ariane 5 Flight 501 在起飞后 40 秒被引爆。因为导航软件里的一个 bug,这个价值 10 亿美金的运载火箭不得不自毁。
  • 1991年2月25日,MIM-104 Patriot(爱国者)里的一个软件错误使它的系统每一百小时有三分之一秒的时钟偏移,导致定位拦截入侵导弹失败。结果伊拉克的飞毛腿导弹击中宰赫兰(沙特阿拉伯东北部城市)的一个美军军营,28 人死亡,100 多人受伤。
  • 其他案例,请参见《Bug 引发的 18 次重大事故》。

这应该能够说明编写安全软件的重要性了,尤其在特定的环境中。当然也包括其他用例中,我们也应该意识到我们的软件 bug 会导致什么后果。

防御性编程初窥

为什么我认为在特定种类的工程中,防御性编程是解决这些问题好办法?

抵御那些不可能的事,因为看似不可能的事也会发生。

防御性编程中有很多防御方式,这也取决于你的软件项目所需的「安全」级别和资源级别。

防御性编程是防御式设计的一种形式,用来确保软件在未知的环境中能继续运行。防御性编程的实践往往用于需要高可用性、安全性、保密性的地方。—— 维基百科

我个人相信这种方法适合很多人参与的大型、长期的项目。例如,一个需要大量维护的开源项目。

我们来探索一下我提出的关键点,来完成一个防御性编程的实现。

永远不要相信用户输入

设想你总是获取到你不想要的东西。因为像我们说过的,我们预期的是异常情况的出现,(所以)要时刻防备用户输入以及通常会传入你系统的东西,这是你成为一个防御性程序员的方法。试着做到尽可能的严格,确保输入的值就是你所期望的值。

进攻是最好的防守

设置白名单而不是黑名单。举个例子,当你验证图像扩展名时,不要检查非法的类型,而是检查合法的类型并排除其他类型。在 PHP 有无数的开源校验库可以让你的工作变简单。

进攻是最好的防守。共勉

数据库抽象化

OWASP Top 10 Security Vulnerabilities 排首位的是注入攻击。这意味着有些人(很多人)还没有使用安全的工具来查询数据库。请使用数据库抽象包或库。在 PHP 里你可以使用 PDO确保基本的注入攻击防范

不要重复发明轮子

你不用框架(或微框架)吗?好吧恭喜你,你喜欢毫无理由地做额外的工作。这并不仅跟框架有关,也意味着你可以方便地使用已经存在的、经过测试的、受万千开发者信任的、稳定的新特性,而不是你只为了自己从中受益而制作的东西。你自己创建方法的唯一原因是你需要的东西不存在,或存在但不符合你的需求(性能差、缺失特性等等)。

这就是所谓的智能代码重用。拥抱它吧。

不要相信开发者

防御性编程与防御性驾驶相关联。在防御性驾驶中,我们假设周围的每个人都可能犯错。所以我们要留意别人的行为。相同概念也适用于防御性编程,我们作为开发者不要相信其他开发者的代码。我们同样也不要相信我们的代码。

在很多人参与的大型项目中,我们有许多方式编写并组织代码。这也导致混乱甚至更多的 bug。这也是我们需要加强规范代码风格和代码检查的原因,让生活更轻松。

编写符合 SOLID 原则的代码

这是(防御性)编程最困难的部分——编写不糟糕的代码。这也是很多人知道并一直在讨论的,但没有人真正关心或将注意力和精力放在实现符合 SOLID 原则的代码上。

让我们看一些糟糕的例子

避免:未初始化的属性

<?php

class BankAccount
{
    protected $currency = null;
    public function setCurrency($currency) { ... }
    public function payTo(Account $to, $amount)
    {
        // sorry for this silly example
        $this->transaction->process($to, $amount, $this->currency);
    }
}

// I forgot to call $bankAccount->setCurrency('GBP');
$bankAccount->payTo($joe, 100);

在这个例子中,我们需要牢记签发付款前要先调用 setCurrency。这是很糟糕的事情,一个像这样的改变状态的操作(签发付款)不应该分两步来完成,且使用两个公开的方法。我们还可以用很多方法付款,但我们必须只有一个公开的方法来改变状态(对象不应该存在不一致的状态)。

在这个例子中,我们把它改进,将未初始化的属性封装进 Money 对象。

<?php

class BankAccount
{
    public function payTo(Account $to, Money $money) { ... }
}

$bankAccount->payTo($joe, new Money(100, new Currency('GBP')));

使它万无一失。不要使用未初始化的对象属性。

避免:在类的作用域外泄露状态

<?php

class Message
{
    protected $content;
    public function setContent($content)
    {
        $this->content = $content;
    }
}

class Mailer
{
    protected $message;
    public function __construct(Message $message)
    {
        $this->message = $message;
    }
    public function sendMessage(
    {
        var_dump($this->message);
    }
}

$message = new Message();
$message->setContent("bob message");
$joeMailer = new Mailer($message);

$message->setContent("joe message");
$bobMailer = new Mailer($message);

$joeMailer->sendMessage();
$bobMailer->sendMessage();

在这个例子中,Message 是通过引用传递的,两个实例的输出都是 “joe message”。一个解决方案是复制 Mailer 构造函数中的 message 对象。但是我们应该做的是试着使用(不可变的)值对象,而不是简单可变的 Message 对象。尽可能使用不可变的对象。

<?php

class Message
{
    protected $content;
    public function __construct($content)
    {
        $this->content = $content;
    }
}

class Mailer 
{
    protected $message;
    public function __construct(Message $message)
    {
        $this->message = $message;
    }
    public function sendMessage()
    {
        var_dump($this->message);
    }
}

$joeMailer = new Mailer(new Message("bob message"));
$bobMailer = new Mailer(new Message("joe message"));

$joeMailer->sendMessage();
$bobMailer->sendMessage();

编写测试

这点我们很还需要再说吗?编写单元测试可以帮助你秉承一般的原则,比如高内聚、单一职责、低耦合和正确的对象组合。它不仅帮助你测试小的单元用例,也能测试你组织对象的方式。确实,当测试你的小功能时,你会清晰的看到你需要测试多少情况和需要模拟多少对象,来达到 100% 的覆盖率。

结论

希望你喜欢这篇文章。记住这些仅仅是建议而已,由你决定何时、何处以及是否应用。

感谢阅读!

想写无Bug的安全代码?看防御性编程的艺术,首发于文章 – 伯乐在线

百亿互金平台救火故事

多年前,又是周六客服打电话过来,平台官网不能访问,app完全无法打开,客户在QQ群和微信群中各种反馈,说平台是不是跑路了?客服的多条400热线完全被打爆,电话已经接不过来…

前言

一直以来总是想以什么方式去记录下自己在互金行业的这段经历,趁着自己还记得清楚,还能找到一些资料原型,一方面可以分享出来供大家参考,但是更重要就是多年以后我可以根据这些文章回忆起来自己的那段激情岁月。

想了很久但一直没有实施,后来觉得应该从架构的角度来梳理一篇文章,就写《从零到百亿互联网金融架构发展史》这篇文章;最后认为只有实战出来的东西以及解决问题的过程,才是工作中最宝贵的经验,应该把它分享出来,在梳理的过程中觉得有三起事故比较有代表性就整理出了下面这三篇文章,本篇文章从整体来回忆一下一路走过来所经历过的救火故事。

作为一个互联网金融平台,涉及到用户资金,任何的服务(资金)差错用户都是不可容忍的,用户不懂什么是数据库,不知道什么网络不通,就是一会看不到钱在app里面展示都会觉得不安。在已经有很多P2P公司跑路的前提下,用户个个已经被锻炼成为福尔摩斯侦探,每天打开app查看收益,监控着平台一切,甚至半夜升级断网十分钟,也会被用户察觉,直接就发到群里面,更有甚者直接在QQ群或者微信群中你们的技术行不行!

我们常说的互联网工作经验,一方面是开发经验,但其实更重要的是处理问题的能力。那么处理问题的能力怎么来呢,就是不断的去解决问题,不断的去总结经验,其中处理生产环境中问题的经验更甚,因为在处理生产环境中对个人的压力和临危应变的能力要求最高,你不但需要面临千万个用户反馈,客服不时得催促而且旁边可能就站了N个领导在看着你,一副你行不行的样子要求立刻马上解决问题!这个时候你的操作就非常重要,稍有不慎便会引发二次生产事故。

说了这么多,只是想说明,生产事故对技术综合能力要求颇高,更是锻炼处理问题能力最佳时机!下面给大家介绍我们从零开发到现在百亿交易量所遇到的几次关键事故,有大有小挑出一些比较有代表性的事件来分享。

并发满标

公司系统刚上线的时候,其实没有经历过什么大量用户并发的考验,结果公司做了一个大的推广,涌入了一批用户来抢标,共1000万的标的几乎都在10秒之内搞定,大概会有上万左右的用户会同时去抢标,平均每秒大概有千人左右的并发,满标控制这块没有经过大的并发测试,上来之后就被打垮了,导致得结果是什么呢,1000万的标的,有可能到一千零几万满标,也有可能会九百多万就满标,也就说要不就是多了一些,要不就是少了一些,就满标了。

这就会很尴尬,因为借款用户就借款一千万整,那么多出来的钱既不能给用户退回了,因为用户好不容易才抢上了,无端退了用户也闹;少了也是问题,用户借款一千万,少了几十万也不行,如果短得少了可以想办法找一些有钱的客户直接给买了,多了就必须重新放出来让用户投资,非常影响士气,这个问题困扰了我们有一段时间。

购买标的流程图,不知道大家是否能根据此图发现问题呢?

超募

为何会产生超募?在最早前的版本中没有使用乐观锁来控制,如果在最后购买的用户一单出现并发,就会出现超募,比如最后剩余30000份的购买份额,因为并发量特别大,可能同时会有十几个用户拿到了剩余30000份余额的可购买额度,有的买1000份、有的买上3000份、有的买上20000份都会驱动满标,所以最后导致了超募。

针对这个问题,主要是引入了memcached乐观锁的概念(底层主要是casgets两个命令),在发标的时候存入标的总份额,当用户购买的时候首先去锁定用户购买的份额,因为乐观锁的原因,如果同时有两个用户拿到份额的时候保证只有一个最后可以更新成功(锁定份额),(锁定份额)失败直接返回,这样就保证了在入口的时候就直接屏蔽了部分并发的请求。

少募
为何产生少募?少募是可能1000万的标的突然到980万就给满标了,这是因为在超募情况下我们完善了代码,用户一进来首先就是锁定购买份额,只有锁定购买份额才能进行下面的流程,如果锁定购买份额失败直接返回,这样虽然保证了在1000万份额在购买初期必须每一个用户只能锁定一份,但是在高并发的情况下,因为购买流程中有十几个分支,每一个分支失败就会退回锁定的份额,这样就会导致这样的现象,就是可能是并发一上来,马上就满标了,过了一会进度就回退回来了。

少募主要是因为分支失败回退导致的,一方面我们分析了容易导致回退热点,因为在用户抢标的时候会给用户实时的展示标的进度,在很早的版本中直接就是存入到一个标的进度表里面,并且采用了乐观锁,如果并发一高就频繁的更新失败导致回退,因此优化了标的进度这块,直接去掉了标的进度表,实时根据查询来展示标的进度(可以有延迟,有缓存);另一方面在回退份额的时候在次判断试下memcached的份额和标的的状态,如果份额不为零并且标的状态是满标,马上自动更新状态保证后续用户可以立即购买再次驱动满标。

做了以上的两种优化后,我们还遇到了其它的一些小问题,在不断的优化过程中,终于稳定下来;在后期版本中将考虑使用MQ队列或者redis队列来处理抢标更合理对用户也更公平一些。

黑客攻击

2015年应该是互联网行业受黑客攻击最多的一年吧,各互金公司都深受其害,其中我就记得网贷之家有一段时间被黑客攻击的太厉害,连续几天网站都无法打开。当然了我们也未能幸免,什么DDOS攻击、SQL注入、寻找系统漏洞等都几乎都经历过了,有的黑客还比较好,应该是出于善意或者展示自己,将漏洞放到乌云上面或者漏洞盒子里面让厂商来修复。但更多的是一些黑产完全就是威胁、敲诈想捞一笔钱,先看看下面这位吧:

这个家伙潜伏到我们公司的客户群里面,冒充我们的客户代表将头像和资料替换成一样,然后给群里所有的客服让他们发送我们内部的后台地址,想通过这种方式来寻找突破口,当然了这个算是里面的小菜鸟吧。

DDOS攻击

DDOS攻击我们也是遇到了很多次,确实也没有比较好办法,最后都是通过一些笨办法来尽量的避免,先说说我们的经历吧。有一次我正在敲代码,客服QQ又闪烁了起来,还没来得及打开查看信息,客服的经理电话就直接打了过来,我立刻就有一种不祥的预感,说官网打不开了,后台也登录不了。

挂了电话,我在本机进行了测试果然不行,立刻准备登录VPN查看服务器各项指标,结果登录不上去,马上上楼找运维经理,他也登录不上,刚准备给机房打电话的时候,机房来电话了,说我们的一个IP正经历着1G多的流量访问,问我们是否正在做什么活动,刚话没有说完就说流量已经到5G,不到一分钟之后流量已经到达18G之多。因为我们的机房和集团公用了一个宽带入口,结果陆续的集团上面反馈他们的网站、服务也都出现了问题,机房方面害怕引起更大的冲击,直接把我们官网对外的IP封掉,集团的其它业务也才慢慢都恢复了过来,我们也紧急的更换了外网IP,重新切换了域名解析才恢复。

事后我们根据apache分析了日志,流量来自N多个不同的IP地址根本无法应对,也正式因为这次攻击也才让我们领导重视了起来,将我们公司的机房网络层和公司集团彻底分离,这样的话不管那方受到大流量攻击都不会相互影响,我们也想了一些笨办法,因为上次我们更换了外网IP之后攻击也就停止了,那么我们认为肯定是针对我们外网来攻击的,所有我们就多准备了6个外网IP,当监控到对某一个外网进行攻击的时候马上切换到另一个外网地址,就这样跟他们玩,可以起到非常有限的一点作用,如果黑客真的想跟我们玩,这个办法就像是小孩子捉迷藏。

周年庆的DDOS攻击

还有一次我们正在做周年庆活动,突然有人在QQ群里面给我们客服说了一句,叫你们的技术负责人来找我,然后我们的网站就挂了,我还保留了当时的一个截图如下:

完了之后客服就来找我,然后按照往常的策略处理完之后,我根据客服给我的QQ号码加上了那个人,开口就来吓我,我依稀记当年的对话如下:

黑客:你是平台的技术负责人吗?
我:算是吧
黑客:你信不信我可以让你们官网在5秒之内挂掉?
我:…(沉默,还真害怕又把官网搞挂了)
黑客:你们的官网漏洞很大
我:如果有好的建议请您赐教
黑客:你们的服务器是不是什么防护软件都没有装?
我:…(继续沉默,这会在想不会是那个安全厂商来推广产品的吧,当然我们基础的防护肯定有)
黑客:我们有非常多的肉鸡,想攻击谁,几秒之内肯定搞定
我:…
黑客:我们已经给很多互联网金融行业做了渗透测试,花点钱帮买你们平安,保证以后不会在出事情
我:…
黑客:免费的策略也有很多,比如360、百度云的安全产品可以免费低档10G左右的流量

……(中间省略)

黑客:我说了这多,你们也是不是给包烟钱,表示表示。

……

后来也和领导进行了商议,坚决不能给他们钱,不能助涨这种嚣张气焰,实在不行就报警!

曝光一下当年使用的假QQ号,刚查了下变了个头像和描述,如下:

后来我一直在想为什么DDOS攻击总是喜欢根据外网IP来攻击呢,慢慢好像是理解了如果针对域名来攻击的话,那不就是攻击到域名商的服务器了吗,一般域名商比较强大,黑客不太搞的定,也确实没有必要。当然记的前一段时间,某著名域名服务商被攻击,导致国外twitter等著名的互联网公司访问不断到达半天以上,还是很严重的。但是对于我们这些小公司,倒不至于搞这么大的动作。

到底如何正确的防止DDOS攻击:

  • 第一种方案,隐藏服务器外网地址,服务器前端加CDN中转,免费的有百度云加速、360网站卫士、加速乐、安全宝等,如果资金充裕的话,可以购买高防的盾机,用于隐藏服务器真实IP,域名解析使用CDN的IP,所有解析的子域名都使用CDN的IP地址。此外,服务器上部署的其他域名也不能使用真实IP解析,全部都使用CDN来解析。
  • 第二种方案,买一些安全产品来进行流量清洗,主要是阿里云、腾讯云这种大厂商提供的一种服务。
  • 第三种方案,有很多的防火墙产品声称可以防止Ddos攻击,但是我个人使用感觉效果非常有限。

SQL注入

我们的官网使用的是PHP开发,因为框架比较老旧的原因,存在着一些SQL注入的点,我们发现了一些进行了修补,没想到还是被一些黑客找到了突破点,这块还是比较感谢这些黑客的在漏洞盒子上面提交了bug(如下图),最后我们根据提示进行了紧急修复,后来我们也在WAF防火墙配置了一些拦截SQL注入的策略,起到双保险的作用。

我一直在想为什么PHP一般比较容易出现SQL注入呢,而Java较少的暴漏出来SQL漏洞的情况,我估摸着有两方面的原因:第一,PHP一般会在前端使用的较多,受攻击的机会更多一些,Java一般做为后端服务攻击的可能性会比较少;第二,PHP框架较多而且很多早期的框架并没有特别考虑SQL注入的情况,Java大量普及了mybaits\hibernate这种orm框架,框架本身对常见的SQL注入有防止的功能,但不是说mybaits/hibernate框架就没有被sql注入的可能,大部分场景下是OK的。另外参数化查询可以有效的避免SQL注入。

通过一段时间的学习,我发黑客一般先使用工具对网站做整体的扫描类似Acunetix,在根据扫描出来的漏洞做个大概的分析,但是比较深入的漏洞都需要根据网站的业务在进行调整,比如sql注入会根据页面的查询使用sqlmap等工具来进一步的渗透。当然我对这方面还是外行,描述的可能不够清晰。

其它攻击

其它方面的攻击,主要是在业务方面,比如我们当初有一个很小的失误,有一个程序员在H5的小网页中将发送短信验证码返回了前端,最后被haker发现了,利用这个漏洞可以给任意的用户重置登录密码;短信攻击,现在的网站几乎都有发送短信或者短信验证码的功能,如果前端不做校验,haker会随便写一个for循环来发短信,一般系统的短信会进行全方位的防控,比如:1、前端加验证(字符验证码,有的是拖拽的动画);2、后端根据用户或者IP加限制,比如用户一分钟只可以发送一条短信,忘记密码的短信一天只能发送10条、一个IP地址限制每天只能发送100条短信等。

BUG

重复派息

15年的某一天看到一个新闻说是陆金所的一个用户发现自己银行里面突然多了很多钱,没过多久又被扣走了,然后收到陆金所那边的解释,说是给用户还本派息的时候程序出现了问题导致还本派息两次,当他们程序员发现了此问题后紧急进行了处理,用户当然闹了呀,就上了新闻,当然陆金所通道能力确实比较强可以直接从用户卡里面扣,当大家都兴致勃勃的谈论这个话题的时候,我却有一股淡淡的忧伤,为什么呢?因为这个错误我们也犯过,具体说就是我搞的,大家可不知道当时的心里压力有多大!

事情是这样子的,我们使用的第三方支付的扣款接口不是特别的稳定,于是我们前期就对接了两种不通的扣款接口,平时前端投资的时候走一个接口,后端派息或者还本的时候走另外的一个接口,在初期的时候扣款接口不稳定,因此在给用户跑批的时候经常会有个别用户失败,需要手动给失败的用户二次派息。做为一个有志向的程序员当然觉得这种方式是低效的,于是将程序改造了一下,在后端派息的时候当第一种扣款失败的时候,自动再次调用第二种扣款接口进行扣款,当时想着这种方式挺好的,各个环境测试也没有问题,上线之后监控过一段时间也运行稳定。

当我感觉一切都很美妙的时候,事故就来了,突然有一天客服反馈说有的用户说自己收到的利息感觉不对,好像是多了(真的是太感谢这个用户了),我登录后台看了一下派息的流水复核了一遍,果然利息被重复派了,一股冷水从头而下,把当天所有的用户派息记录和到期记录都进行了检查,影响了70多个用户,导致多派息了6万多元,幸亏只是派息出了问题,如果是到期的话金额会翻N倍,其中70多个人里面有几个进行了提现、几个进行了再次投资,绝大部分用户在我们发现的时候还不知情,金额也没有动。

怎么处理呢,当然不能直接就动用户的钱了,给每个重复派息的用户打电话,说明原因赠送小礼物,请求谅解后我们把重复派过的利息在次调回来。大部分用户进行了核对之后都还是比较配合的,但是肯定有一些用户不干了,当然也不能怪客户,都是我的原因,有的客户需要上门赔礼道歉,有的客户需要公司出具证明材料,我们的老板亲自给客户打了N个电话被客户骂了N遍,我心里压力可想而知,其中有一个客户特别难缠,各种威胁说既然到了我的账户里面肯定是我的,你们的失误不应该让他来承担,折腾了很久,还是不能怪客户。可能会说有的互联网公司经常出现这种问题后就送给客户了,哎,我们是小公司呀!这个噱头玩不起。

到底是什么原因呢,事后进行了复盘也给领导做了汇报,平时都是首先进行派息的定时任务,过一个小时之后进行到期的定时任务,当天的派息标的比较多,跑了一个半小时,就导致了派息和到期的两个定时任务同时进行,转账有了并发,第三方支付的接口不稳定给我们返回的失败,其实有的是成功的,就导致了我们进行了二次的扣款尝试引发了此问题。这个事情给我带来了非常大的教训,对于金融扣款的这种事情一定需要谨慎,哪怕付款引发报警之后在人工处理,也不能盲目重试可能引发雪崩效应。

杂七杂八

还有就是其它一些零碎的问题了,记的有一次对用户的登录过程进行优化,导致有一块判断少了一个括号结果用户在那两个小时内,只要输入账户,任意密码就可以登录了,幸好及时发现这个问题,正是这个问题才导致了我们正式确立了规范的上线流程,为以后的上线制度建定了基础。

还有一次我们在模拟用户投资一种标的时候,留了一个入口通过http就可以调用,测试也没有问题,有一天正好给领导演示呢,就在次用http请求的方式在浏览器执行了一下,前端就会看到自动投标的过程,因为生产的数据有点多,投标的过程有点长,我们为了加快进度,找了好几个人同时来执行这http请求,导致最后出现了问题,最后发现写测试脚本的这个同事根本就没有考虑并发的情况,才导致出现了问题。

也做了很多的活动,记得做一个网贷之家的一个活动的时候,活动上线比较紧张,我们团队曾经连续工作超过30个小时(一天一夜再一天),当天晚上我2点左右写完程序,测试从2两点测试到早上9点,最终确认没有任何问题,才进行投产。半夜公司没有暖气,我们实在冻的不行了,就在办公室跑步,从这头跑到那头,第二天上线之后,又害怕出现问题,监控了一天,确认没有任何问题,才到下午正常下班回家,那时候真是激情满满呀。

说到做活动肯定少了羊毛党,说哪一家互金公司没有遇到过羊毛党那很少见,而且现在的羊毛党规模简直逆天了,我们用户里面就有一个羊毛党在两三天之内邀请了六七千位用户,如果说邀请一个用户送1元,那这个用户就可以搞几千块一次,而且有很多专业的网站、QQ群、微信公共账号都是他们的聚集地,那天那个平台有活动门清,他们写的淘羊毛操作手册有时候比我们官网的帮助文档还清晰,所以做活动的时候要考虑特别周全,各种限制,有封定、有预案、讲诚信,只要是符合我们活动规则的坚决按照流程走。

还有一个有趣的事情,app推送,一次我在公交车上就看到xx盒子app弹出hhhhh的推送,这个事情我们也搞过,因为在调试的时候生产和测试就差了一个参数,有时候开发人员不注意就把生产参数部署到uat环境了,测试一发送就跑到生产了,这方面只能严格流产管理来防止了。

其实还很多问题:mongodb集群和mysql的同步出现的一些状况、后台大量数据查询下的sql优化、golang使用mapreduce碰到的问题… 限于篇幅这里就不一一清晰的描述了。

其实每次的出现问题都是对团队一次非常好的锻炼机会,通过发现问题,定位问题,解决问题,再次回过头来反思这些问题;重新梳理整个环节, 举一反三避免下次再次出现类似的问题。正是因为经历这些种种的困难、考验才让团队变的更强大更稳定,也更体现了流程的重要性,更是避免再次发生类似问题。

总结

古代对将军的要求是,心有万马奔腾而过,而面平静如湖水可照镜,在互联网行业对大牛的要求也同如此,特别是技术的负责人,在面对生产事故的时候,一定首先是安抚同事,静下下心来找到问题本质在去解决,而不是不断去施加压力催促解决,重压之下很多心里承受能力稍弱的队友,更加慌乱而不利于解决问题或者引发二次事故。

在看淘宝双十一视频中,有一段特别受到感触,在双十一初期,虽然技术团队做了很多的准备,但是在零点过后流量瞬间涌入,服务被打垮,部分用户投诉刷新不出网页,紧接着隔壁同事也都反馈网站打不开,在大家都在慌乱中,xx一拍桌子大喊一声,大家都别动,三分钟之后再说,过了几分钟之后服务慢慢部分恢复了正常。后来回忆说,当时虽然服务瘫痪,但是监控还是有部分得业务成功,说明系统并没有被压垮,而此时的任何操作都有可能引发更大的问题,从此之后此人一战成名,成为阿里大将。

互联网平台发展大抵都会经历三个阶段:

  • 1、上线初期,此阶段问题最为繁多,生产事故不断,系统快速迭代优化。有人说为什么不测试到完全没有问题在投产吗?说实话在互联网行业这个很难,第一小公司很难做到生产环境和测试环境一致,成本太高;时间紧迫,一般都是很短的时间内要求上线,上线之后在快速迭代。另外互联网本就是一个快速试错的行业,错过半年时间可能风口早过;
  • 2、发展期,此阶段主要业务模式已经得到验证,系统出现问题的频繁度较少,低级错误减少,但此时是用户量和交易量不断爆发的时候,对系统性能、高并发的要求又上来了,所以此时出现的问题大多都是性能的问题;
  • 3、成熟期,发展期过后系统相对比较平稳,用户量和交易量都已经慢慢稳定下来,生产问题越来越少,出现问题几乎都是细小的bug,这个阶段也是公司最忽略技术得阶段,恰好我们公司就处于这个阶段,在这个阶段就需要静下心里,组织架构升级,补齐在初期和发展起所欠的技术债务,做好公司在升下一个量级的技术准备。

所有的这些问题几乎都集中在14年底到15年初的这个阶段,15年后半年开始到现在平台慢慢稳定了下来,到现在几乎没有再出现过类似的问题,也因为几乎都是两年前的事情,有很多记的不是特别清楚了,写的比较粗糙望见谅。

百亿互金平台救火故事,首发于文章 – 伯乐在线

如何用 OllyDbg 的跟踪功能分析虚拟机保护

虚拟机保护已经是现代保护壳不可缺少的一环,虽然逆向方也发展出各种插件帮助分析,但只针对特定某款,通用性的方法却不多见。我总在想,既然虚拟机的结构是固定的,如果有一款工具能够记录指令流,那么按图索骥,也许能发展出一套通用的分析方法来。其实OD(OllyDbg)就有记录指令流的功能,叫跟踪(trace),也许是效果不好或者操作不便,用的人甚至知道的人不多。先介绍下怎么用。

OD的跟踪功能原理很简单,就是每一步都自动下单步断点,然后记录断下来的指令信息。这项功能涉及到几项设置,第一项是缓存大小,不难想象,跟踪得到的这一些列的指令记录是需要占地方存储的,占多大可以设置,位置在调试选项(Debugging options)->跟踪(Trace),如图1。

file0001

图1

第一项就是缓存的大小,内存允许的话,自然是多多益善,毕竟缓存越大,允许记录的信息越多。第二项是记录的内容,跟踪会自动记录地址模块等信息,此外可以选择是否记录指令、ESP和标志位的信息。设置位置紧接着缓存大小,见图2,可以按需勾选,本文只需要记录指令即可。最后一项是在调试(DEBUG)菜单中打开Trace。

file0002

图2

现在Trace已经设置完毕了,按下Ctrl+F12,查看Trace窗口,应该已经开始记录执行过的指令。否则请检查前述设置和操作是否正确。

那么,虚拟机保护要怎么入手分析呢?前面我提到,虚拟机是有固定结构的,既然要分析,那对应的找到这些结构应该就可以了。传统保护虚拟机的结构其实很简单,大致可以看成一只章鱼,有三个部分,分别是init(头),Dispatch(身)和Handle(触须),如图3:

file0003

图3

Init主要完成虚拟机初始化工作,例如申请内存填写初始值之类,每次进入虚拟机,这个“头部”通常只执行一次。Dispatch是虚拟机的主体,可以看成一个主循环,它是每一条虚拟机指令的开始之处,也是结束之处,负责读取虚拟机指令,进入具体handle解释等工作。Handle就是虚拟机的“指令”了,实际完成各项虚拟机指令的功能。 我曾写过一篇《基于虚拟机的软件保护技术》较为详细的介绍过虚拟机保护技术,对基本结构还不太熟的同学,此文会对上述概念有更详细的说明。

现在,我们就要在具体的软件中找这只“章鱼”了。以一个CrackMe为例,首先清理所有断点,打开Trace,Ctrl+F12跟踪步过运行,看到程序跑起来了,F12暂停,看Trace的窗口如下(图4):

file0004

图4

记录是从下往上看的,可以看出,在程序空间的最后一条支流,是00401534的一个call,调用了DialogBoxInDirectParamA,这是一个调出系统对话框的API,其中有一个参数DlgProc用来指明消息回调函数的位置,我们直接在反汇编窗口查看这个API,发现回调函数是0x401572(图5):

file0005

图5

0x401572处代码不长,有好几条Call,但大部分都是系统Call,只有一处调用了程序空间的函数,这个函数就是虚拟机的入口。到这里,我们对虚拟机的分析的工作才刚刚开始。

首先对虚拟机的入口下断,然后重新运行程序。目的是保证能够正确找到init。现在应该端在虚拟机的入口处,如下图:

file0006

图6

这是个非常简单的虚拟机,有经验的同学也许可以一眼就看出来图6包含了Init和Dispatch分别在哪里。当然也可以用Trace快速找出虚拟机的各个结构。现在去掉断点,打开Trace,Ctrl+F12跟踪步过,这时程序会跑起来,多点击几下按钮,目的是让主要分支得到更充分的执行(即增加获得执行的次数),然后F12暂停。回到Trace窗口,对着任意一行程序空间的指令点击右键,选择模块统计,结果如下图:

file0007

图7

统计是以代码段来划分的,第一栏显示的是这段代码在刚才的跟踪执行中执行的次数,第二栏显示了某个代码段的首地址。我们先找执行了一次的指令首地址。可以找到第5行的地址就是虚拟机的入口地址,点击在反汇编窗口跟随,可以看到这段代码是从0x00401060到0x004010B9,这就是init:

00401060 $ 55 push ebp
00401061 . 8BEC mov ebp, esp
00401063 . 81C4 D0FEFFFF add esp, -0x130
00401069 . C745 E4 00000>mov dword ptr [ebp-0x1C], 0x0
00401070 . C745 E8 00000>mov dword ptr [ebp-0x18], 0x0
00401077 . C745 F1 00000>mov dword ptr [ebp-0xF], 0x0
0040107E . C645 FD 00 mov byte ptr [ebp-0x3], 0x0
00401082 . C645 FE 00 mov byte ptr [ebp-0x2], 0x0
00401086 . C745 F5 00000>mov dword ptr [ebp-0xB], 0x0
0040108D . 8D85 D0FEFFFF lea eax, dword ptr [ebp-0x130]
00401093 . 8945 F1 mov dword ptr [ebp-0xF], eax
00401096 . 8B45 14 mov eax, dword ptr [ebp+0x14]
00401099 . 8945 E0 mov dword ptr [ebp-0x20], eax
0040109C . 8B45 08 mov eax, dword ptr [ebp+0x8]
0040109F . 8945 D0 mov dword ptr [ebp-0x30], eax
004010A2 . 8B45 0C mov eax, dword ptr [ebp+0xC]
004010A5 . 8945 D8 mov dword ptr [ebp-0x28], eax
004010A8 . C745 DC 00000>mov dword ptr [ebp-0x24], 0x0
004010AF . C745 D4 00000>mov dword ptr [ebp-0x2C], 0x0
004010B6 . 8B45 10 mov eax, dword ptr [ebp+0x10]
004010B9 . 8945 EC mov dword ptr [ebp-0x14], eax

接着找Dispatch,刚才说过,它既是虚拟机指令的开始,又是结束,它得到的执行次数一定也最多。可以看到第三行的0x004010B9,这个地址在虚拟机入口地址之后,执行次数最多,同样的办法可以看到这段代码的终止位置是0x004010D9:

004010BC > /FF45 EC inc dword ptr [ebp-0x14]
004010BF . |8B45 EC mov eax, dword ptr [ebp-0x14]
004010C2 . |8A00 mov al, byte ptr [eax]
004010C4 . |8845 F0 mov byte ptr [ebp-0x10], al
004010C7 . |B8 00204000 mov eax, 00402000
004010CC . |0FB65D F0 movzx ebx, byte ptr [ebp-0x10]
004010D0 . |C1E3 02 shl ebx, 0x2
004010D3 . |03C3 add eax, ebx
004010D5 . |FF20 jmp dword ptr [eax]

最后是找这次执行虚拟机用到的handle。这个不难,虚拟机入口地址之后的代码段除了init和dispatch,其它都是handle,所有执行过的handle都会在里面出现。当然了,某条handle的具体作用,以及没有执行过的handle,就只能靠人肉分析了。还有就是,就分析虚拟机保护来说,了解执行了哪些handle,以及哪些handle更常用,这些信息都是十分有用的。

如何用 OllyDbg 的跟踪功能分析虚拟机保护,首发于文章 – 伯乐在线

解决PKIX问题:unable to find valid certification path to requested target

话说前几天在测试服务器上遇到了这么个异常

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

就是说找不着安全证书啥的等等烂码七糟的一大堆

接着就拜Google大神,发现一篇文章能被N个人转来转去的,关键文章还不怎么靠谱

后来找到了一个办法,幸运的是在测试环境一弄, 这个问题看上去就被解决了

我们要做的就是将所要访问的URL的安全认证证书导入到客户端

下面是获取安全证书的一种方法

/*
 * Copyright 2006 Sun Microsystems, Inc.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Sun Microsystems nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

public class InstallCert {
	public static void main(String[] args) throws Exception {
		String host;
		int port;
		char[] passphrase;
		if ((args.length == 1) || (args.length == 2)) {
			String[] c = args[0].split(":");
			host = c[0];
			port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
			String p = (args.length == 1) ? "changeit" : args[1];
			passphrase = p.toCharArray();
		} else {
			System.out.println("Usage: java InstallCert <host>[:port] [passphrase]");
			return;
		}

		File file = new File("jssecacerts");
		if (file.isFile() == false) {
			char SEP = File.separatorChar;
			File dir = new File(System.getProperty("java.home") + SEP + "lib" + SEP + "security");
			file = new File(dir, "jssecacerts");
			if (file.isFile() == false) {
				file = new File(dir, "cacerts");
			}
		}
		
		System.out.println("Loading KeyStore " + file + "...");
		InputStream in = new FileInputStream(file);
		KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
		ks.load(in, passphrase);
		in.close();

		SSLContext context = SSLContext.getInstance("TLS");
		TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
		tmf.init(ks);
		X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
		SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
		context.init(null, new TrustManager[]{tm}, null);
		SSLSocketFactory factory = context.getSocketFactory();

		System.out.println("Opening connection to " + host + ":" + port + "...");
		SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
		socket.setSoTimeout(10000);
		try {
			System.out.println("Starting SSL handshake...");
			socket.startHandshake();
			socket.close();
			System.out.println();
			System.out.println("No errors, certificate is already trusted");
		} catch (SSLException e) {
			System.out.println();
			e.printStackTrace(System.out);
		}

		X509Certificate[] chain = tm.chain;
		if (chain == null) {
			System.out.println("Could not obtain server certificate chain");
			return;
		}

		BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

		System.out.println();
		System.out.println("Server sent " + chain.length + " certificate(s):");
		System.out.println();
		MessageDigest sha1 = MessageDigest.getInstance("SHA1");
		MessageDigest md5 = MessageDigest.getInstance("MD5");
		for (int i = 0; i < chain.length; i++) {
			X509Certificate cert = chain[i];
			System.out.println(" " + (i + 1) + " Subject " + cert.getSubjectDN());
			System.out.println("   Issuer  " + cert.getIssuerDN());
			sha1.update(cert.getEncoded());
			System.out.println("   sha1    " + toHexString(sha1.digest()));
			md5.update(cert.getEncoded());
			System.out.println("   md5     " + toHexString(md5.digest()));
			System.out.println();
		}

		System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]");
		String line = reader.readLine().trim();
		int k;
		try {
			k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
		} catch (NumberFormatException e) {
			System.out.println("KeyStore not changed");
			return;
		}

		X509Certificate cert = chain[k];
		String alias = host + "-" + (k + 1);
		ks.setCertificateEntry(alias, cert);

		OutputStream out = new FileOutputStream("jssecacerts");
		ks.store(out, passphrase);
		out.close();

		System.out.println();
		System.out.println(cert);
		System.out.println();
		System.out.println("Added certificate to keystore 'jssecacerts' using alias '" + alias + "'");
	}

	
	private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();

	
	private static String toHexString(byte[] bytes) {
		StringBuilder sb = new StringBuilder(bytes.length * 3);
		for (int b : bytes) {
			b &= 0xff;
			sb.append(HEXDIGITS[b >> 4]);
			sb.append(HEXDIGITS[b & 15]);
			sb.append(' ');
		}
		return sb.toString();
	}

	
	private static class SavingTrustManager implements X509TrustManager {
		private final X509TrustManager tm;
		private X509Certificate[] chain;

		SavingTrustManager(X509TrustManager tm) {
			this.tm = tm;
		}

		public X509Certificate[] getAcceptedIssuers() {
			throw new UnsupportedOperationException();
		}

		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			throw new UnsupportedOperationException();
		}

		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			this.chain = chain;
			tm.checkServerTrusted(chain, authType);
		}
	}
}

编译InstallCert.java得到两个class文件,并执行InstallCert类

执行方式:java InstallCert hostname     eg:java InstallCert www.cebbank.com

接下来会看到下面的打印信息

java InstallCert www.cebbank.com
Loading KeyStore /usr/java/jdk1.6.0_31/jre/lib/security/cacerts...
Opening connection to www.cebbank.com:443...
Starting SSL handshake...

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1731)
	at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:241)
	at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:235)
	at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1206)
	at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:136)
	at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Handshaker.java:593)
	at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Handshaker.java:529)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:925)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1170)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1197)
	at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1181)
	at InstallCert.main(InstallCert.java:102)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:323)
	at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:217)
	at sun.security.validator.Validator.validate(Validator.java:218)
	at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:126)
	at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:209)
	at InstallCert$SavingTrustManager.checkServerTrusted(InstallCert.java:198)
	at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1198)
	... 8 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:174)
	at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:238)
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:318)
	... 14 more

Server sent 1 certificate(s):

 1 Subject CN=www.cebbank.com, OU=Terms of use at www.verisign.com/rpa (c)05, OU=CEB, O="China Everbright Bank Co., Ltd", L=Beijing
   Issuer  CN=VeriSign Class 3 Extended Validation SSL CA, OU=Terms of use at https://www.verisign.com/rpa (c)06, OU=VeriSign Trust Network
   sha1    5b d2 85 6e b3 a4 2b 07 a2 13 47 b3 be 3e 1f c9 d3 ce 46 57 
   md5     05 d8 ae ee f1 d9 51 63 6d 2f 11 e0 ac d0 e7 d7 

Enter certificate to add to trusted keystore or 'q' to quit: [1]

然后输入 1 并回车,会看到类似下面的打印信息

[
[
  Version: V3
  Subject: CN=www.cebbank.com, OU=Terms of use at www.verisign.com/rpa (c)05, OU=CEB, O="China Everbright Bank Co., Ltd", L=Beijing
  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5

  Key:  Sun RSA public key, 2048 bits
  modulus: 30831246384548809540705228292841393062583732250993909916355780413722161557074568469738254573472093341710481517139910877
  public exponent: 65537
  Validity: [From: Mon Jul 02 08:00:00 CST 2012,
               To: Thu Jul 03 07:59:59 CST 2014]
  Issuer: CN=VeriSign Class 3 Extended Validation SSL CA, OU=Terms of use at https://www.verisign.com/rpa (c)06, OU=VeriSign Trust Network
  SerialNumber: [    5715ab25 6be8fa42 2fa28dd4 601bc732]

Certificate Extensions: 9
[1]: ObjectId: 1.3.6.1.5.5.7.1.1 Criticality=false
AuthorityInfoAccess [
  [
   accessMethod: 1.3.6.1.5.5.7.48.1
   accessLocation: URIName: http://ocsp.verisign.com, 
   accessMethod: 1.3.6.1.5.5.7.48.2
   accessLocation: URIName: http://EVSecure-aia.verisign.com/EVSecure2006.cer]
]

[2]: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
  DNSName: www.cebbank.com
]

[3]: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: FC 8A 50 BA 9E B9 25 5A   7B 55 85 4F 95 00 63 8F  ..P...%Z.U.O..c.
0010: E9 58 6B 43                                        .XkC
]

]

[4]: ObjectId: 2.5.29.32 Criticality=false
CertificatePolicies [
  [CertificatePolicyId: [2.16.840.1.113733.1.7.23.6]
[PolicyQualifierInfo: [
  qualifierID: 1.3.6.1.5.5.7.2.1
  qualifier: 0000: 16 1C 68 74 74 70 73 3A   2F 2F 77 77 77 2E 76 65  ..https://www.ve
0010: 72 69 73 69 67 6E 2E 63   6F 6D 2F 63 70 73        risign.com/cps

]]  ]
]

[5]: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
  CA:false
  PathLen: undefined
]

[6]: ObjectId: 1.3.6.1.5.5.7.1.12 Criticality=false
Extension unknown: DER encoded OCTET string =
0000: 04 62 30 60 A1 5E A0 5C   30 5A 30 58 30 56 16 09  .b0`.^.\0Z0X0V..
0010: 69 6D 61 67 65 2F 67 69   66 30 21 30 1F 30 07 06  image/gif0!0.0..
0020: 05 2B 0E 03 02 1A 04 14   4B 6B B9 28 96 06 0C BB  .+......Kk.(....
0030: D0 52 38 9B 29 AC 4B 07   8B 21 05 18 30 26 16 24  .R8.).K..!..0&.$
0040: 68 74 74 70 3A 2F 2F 6C   6F 67 6F 2E 76 65 72 69  http://logo.veri
0050: 73 69 67 6E 2E 63 6F 6D   2F 76 73 6C 6F 67 6F 31  sign.com/vslogo1
0060: 2E 67 69 66                                        .gif


[7]: ObjectId: 2.5.29.37 Criticality=false
ExtendedKeyUsages [
  serverAuth
  clientAuth
]

[8]: ObjectId: 2.5.29.31 Criticality=false
CRLDistributionPoints [
  [DistributionPoint:
     [URIName: http://EVSecure-crl.verisign.com/EVSecure2006.crl]
]]

[9]: ObjectId: 2.5.29.15 Criticality=false
KeyUsage [
  DigitalSignature
  Key_Encipherment
]

]
  Algorithm: [SHA1withRSA]
  Signature:
0000: 42 0A 89 BF 48 08 1E F4   98 F2 E5 DB 0D 83 EF 37  B...H..........7
0010: EC 27 6F 4D 81 69 C6 4A   4C 17 EC 57 F5 48 2A 14  .'oM.i.JL..W.H*.
0020: 3C 54 B2 C5 49 39 42 BA   EC 83 78 02 F9 96 6C 63  <T..I9B...x...lc
0030: 80 BC 60 61 BB 20 D1 AD   C3 D3 76 47 6F 0C 7B AC  ..`a. ....vGo...
0040: 76 B2 C7 2D B1 0A 7A 00   CA 40 38 86 FF 9F 12 F5  v..-..z..@8.....
0050: BE 5A E7 42 97 2F DF DE   0C 19 C5 F6 92 58 17 7A  .Z.B./.......X.z
0060: 9A 1D 2C 2C DA 8B 83 83   2D BE 07 58 56 36 92 E7  ..,,....-..XV6..
0070: B1 F8 A0 B5 00 F4 C3 30   D1 34 37 3D 94 75 28 04  .......0.47=.u(.
0080: A2 D8 C3 FE B1 E1 C2 2E   51 A8 6F D5 09 6D 49 DB  ........Q.o..mI.
0090: 2E 1D 4B F7 A8 06 30 B4   97 E7 C2 33 26 FD 6A DF  ..K...0....3&.j.
00A0: D6 B0 10 A1 F2 73 DD 5A   60 DE 51 5E EA 80 46 86  .....s.Z`.Q^..F.
00B0: 25 0B 53 FC C2 57 80 35   09 2D 31 55 28 35 EE 0F  %.S..W.5.-1U(5..
00C0: 62 50 4B 12 75 0B 02 9F   2F 0B D2 8A 0D 23 E3 C1  bPK.u.../....#..
00D0: 48 28 56 33 E1 DE 31 DD   72 78 15 96 EE 2B A5 1D  H(V3..1.rx...+..
00E0: 37 85 1B E5 88 53 80 88   02 6D 90 F3 E6 4A 74 AC  7....S...m...Jt.
00F0: D2 CA 0E 04 BC 46 A0 57   34 FA CF 9D E5 D7 0E 4B  .....F.W4......K

]

Added certificate to keystore 'jssecacerts' using alias 'www.cebbank.com-1'

同时我们会在当面目录下发现已经生成了一个名为jssecacerts的证书

再将名为jssecacerts的证书拷贝\\%JAVA_HONME%\\jre\\lib\\security\\目录中

最后重启下应用的服务,证书就会生效了。。

补充: 有人说生成证书后不用拷贝,直接代码里加句话就行,结果试了一下发现不管用

System.setProperty("javax.net.ssl.trustStore", "jssecacerts证书路径");

[转]openssl的证书格式转换

证书转换

PKCS 全称是 Public-Key Cryptography Standards ,是由 RSA 实验室与其它安全系统开发商为促进公钥密码的发展而制订的一系列标准,PKCS 目前共发布过 15 个标准。 常用的有:
PKCS#7 Cryptographic Message Syntax Standard
PKCS#10 Certification Request Standard
PKCS#12 Personal Information Exchange Syntax Standard
X.509是常见通用的证书格式。所有的证书都符合为Public Key Infrastructure (PKI) 制定的 ITU-T X509 国际标准。
PKCS#7 常用的后缀是: .P7B .P7C .SPC
PKCS#12 常用的后缀有: .P12 .PFX
X.509 DER 编码(ASCII)的后缀是: .DER .CER .CRT
X.509 PAM 编码(Base64)的后缀是: .PEM .CER .CRT
.cer/.crt是用于存放证书,它是2进制形式存放的,不含私钥。
.pem跟crt/cer的区别是它以Ascii来表示。
pfx/p12用于存放个人证书/私钥,他通常包含保护密码,2进制方式
p10是证书请求
p7r是CA对证书请求的回复,只用于导入
p7b以树状展示证书链(certificate chain),同时也支持单个证书,不含私钥。

1. CA证书

用openssl创建CA证书的RSA密钥(PEM格式):

openssl genrsa -des3 -out ca.key 1024

2. 创建CA证书有效期为一年

用openssl创建CA证书(PEM格式,假如有效期为一年):

openssl req -new -x509 -days 365 -key ca.key -out ca.crt -config openssl.cnf

openssl是可以生成DER格式的CA证书的,最好用IE将PEM格式的CA证书转换成DER格式的CA证书。

3. x509转换为pfx

openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt

4. PEM格式的ca.key转换为Microsoft可以识别的pvk格式

pvk -in ca.key -out ca.pvk -nocrypt -topvk

5. PKCS#12 到 PEM 的转换

openssl pkcs12 -nocerts -nodes -in cert.p12 -out private.pem  验证   openssl pkcs12 -clcerts -nokeys -in cert.p12 -out cert.pem

6. 从 PFX 格式文件中提取私钥格式文件 (.key)

openssl pkcs12 -in mycert.pfx -nocerts -nodes -out mycert.key

7. 转换 pem 到到 spc

   openssl crl2pkcs7 -nocrl -certfile venus.pem  -outform DER -out venus.spc

用 -outform -inform 指定 DER 还是 PAM 格式。例如:

openssl x509 -in Cert.pem -inform PEM -out cert.der -outform DER

8. PEM 到 PKCS#12 的转换

openssl pkcs12 -export -in Cert.pem -out Cert.p12 -inkey key.pem

IIS 证书

cd c:\openssl            set OPENSSL_CONF=openssl.cnf            openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt

server.key和server.crt文件是Apache的证书文件,生成的server.pfx用于导入IIS

9. How to Convert PFX Certificate to PEM Format for SOAP

$ openssl pkcs12 -in test.pfx -out client.pem  Enter Import Password:  MAC verified OK  Enter PEM pass phrase:  Verifying - Enter PEM pass phrase:

Docker 笔记二:安装Nginx

docker pull nginx

I’d like to install nginx as a load balance server.

http {
include       mime.types;
default_type  application/octet-stream;
#定义日志格式
#log_format  main  '$remote_addr - $remote_user [$time_local] $request '
#                  '"$status" $body_bytes_sent "$http_referer" '
#                  '"$http_user_agent" "$http_x_forwarded_for"';
#access_log  off;
access_log  logs/access.log;
client_header_timeout  3m;
client_body_timeout    3m;
send_timeout           3m;
client_header_buffer_size    1k;
large_client_header_buffers  4 4k;
sendfile        on;
tcp_nopush      on;
tcp_nodelay     on;
#keepalive_timeout  75 20;
include    gzip.conf;
upstream localhost {
#根据ip计算将请求分配各那个后端tomcat,许多人误认为可以解决session问题,其实并不能。
#同一机器在多网情况下,路由切换,ip可能不同
#ip_hash;
server localhost:18081;
server localhost:18080;
}
server {
listen       80;
server_name  localhost;
location / {
proxy_connect_timeout   3;
proxy_send_timeout      30;
proxy_read_timeout      30;
proxy_pass http://localhost;
}
}


useful link
https://hub.docker.com/_/nginx/